You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9200 lines
327 KiB

From aa65caa35ae1c69b8b6644c1a72eb8110500e7e0 Mon Sep 17 00:00:00 2001
From: Mohammed Sadiq <sadiq@sadiqpk.org>
Date: Fri, 4 Oct 2019 12:27:26 +0530
Subject: [PATCH 1/7] common: Add polkit rules for modem management
---
panels/common/gnome-control-center.rules.in | 1 +
1 file changed, 1 insertion(+)
diff --git a/panels/common/gnome-control-center.rules.in b/panels/common/gnome-control-center.rules.in
index 971ffac63..22cf785e0 100644
--- a/panels/common/gnome-control-center.rules.in
+++ b/panels/common/gnome-control-center.rules.in
@@ -1,6 +1,7 @@
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.locale1.set-locale" ||
action.id == "org.freedesktop.locale1.set-keyboard" ||
+ action.id == "org.freedesktop.ModemManager1.Device.Control" ||
action.id == "org.freedesktop.hostname1.set-static-hostname" ||
action.id == "org.freedesktop.hostname1.set-hostname" ||
action.id == "org.gnome.controlcenter.datetime.configure") &&
--
2.32.0
From 2b863f51831bc09d3ef56d16858724191ac9095c Mon Sep 17 00:00:00 2001
From: Mohammed Sadiq <sadiq@sadiqpk.org>
Date: Fri, 4 Oct 2019 22:16:23 +0530
Subject: [PATCH 2/7] wwan: Add new panel for modem management
The panel supports 2G/3G/4G GSM/LTE modems. CDMA2000 Modems are not supported.
If a supported modem is present, the panel will be shown and the modem will be
handled, else, network-panel shall manage the modem as it did in the past.
If more than one modem with data enabled is present, the user is allowed to set
priority of one SIM over the other (the priority is for SIM, not modem).
Fixes https://gitlab.gnome.org/GNOME/gnome-control-center/issues/132
---
meson.build | 4 +
panels/meson.build | 3 +-
panels/wwan/cc-wwan-apn-dialog.c | 424 ++++++
panels/wwan/cc-wwan-apn-dialog.h | 40 +
panels/wwan/cc-wwan-apn-dialog.ui | 249 ++++
panels/wwan/cc-wwan-data.c | 1446 ++++++++++++++++++++
panels/wwan/cc-wwan-data.h | 93 ++
panels/wwan/cc-wwan-details-dialog.c | 257 ++++
panels/wwan/cc-wwan-details-dialog.h | 40 +
panels/wwan/cc-wwan-details-dialog.ui | 320 +++++
panels/wwan/cc-wwan-device-page.c | 634 +++++++++
panels/wwan/cc-wwan-device-page.h | 42 +
panels/wwan/cc-wwan-device-page.ui | 270 ++++
panels/wwan/cc-wwan-device.c | 1355 ++++++++++++++++++
panels/wwan/cc-wwan-device.h | 152 ++
panels/wwan/cc-wwan-errors-private.h | 104 ++
panels/wwan/cc-wwan-mode-dialog.c | 327 +++++
panels/wwan/cc-wwan-mode-dialog.h | 40 +
panels/wwan/cc-wwan-mode-dialog.ui | 57 +
panels/wwan/cc-wwan-network-dialog.c | 443 ++++++
panels/wwan/cc-wwan-network-dialog.h | 40 +
panels/wwan/cc-wwan-network-dialog.ui | 188 +++
panels/wwan/cc-wwan-panel.c | 929 +++++++++++++
panels/wwan/cc-wwan-panel.h | 36 +
panels/wwan/cc-wwan-panel.ui | 336 +++++
panels/wwan/cc-wwan-sim-lock-dialog.c | 310 +++++
panels/wwan/cc-wwan-sim-lock-dialog.h | 40 +
panels/wwan/cc-wwan-sim-lock-dialog.ui | 306 +++++
panels/wwan/gnome-wwan-panel.desktop.in.in | 16 +
panels/wwan/meson.build | 61 +
panels/wwan/wwan.gresource.xml | 12 +
shell/cc-panel-list.c | 1 +
shell/cc-panel-loader.c | 9 +
33 files changed, 8583 insertions(+), 1 deletion(-)
create mode 100644 panels/wwan/cc-wwan-apn-dialog.c
create mode 100644 panels/wwan/cc-wwan-apn-dialog.h
create mode 100644 panels/wwan/cc-wwan-apn-dialog.ui
create mode 100644 panels/wwan/cc-wwan-data.c
create mode 100644 panels/wwan/cc-wwan-data.h
create mode 100644 panels/wwan/cc-wwan-details-dialog.c
create mode 100644 panels/wwan/cc-wwan-details-dialog.h
create mode 100644 panels/wwan/cc-wwan-details-dialog.ui
create mode 100644 panels/wwan/cc-wwan-device-page.c
create mode 100644 panels/wwan/cc-wwan-device-page.h
create mode 100644 panels/wwan/cc-wwan-device-page.ui
create mode 100644 panels/wwan/cc-wwan-device.c
create mode 100644 panels/wwan/cc-wwan-device.h
create mode 100644 panels/wwan/cc-wwan-errors-private.h
create mode 100644 panels/wwan/cc-wwan-mode-dialog.c
create mode 100644 panels/wwan/cc-wwan-mode-dialog.h
create mode 100644 panels/wwan/cc-wwan-mode-dialog.ui
create mode 100644 panels/wwan/cc-wwan-network-dialog.c
create mode 100644 panels/wwan/cc-wwan-network-dialog.h
create mode 100644 panels/wwan/cc-wwan-network-dialog.ui
create mode 100644 panels/wwan/cc-wwan-panel.c
create mode 100644 panels/wwan/cc-wwan-panel.h
create mode 100644 panels/wwan/cc-wwan-panel.ui
create mode 100644 panels/wwan/cc-wwan-sim-lock-dialog.c
create mode 100644 panels/wwan/cc-wwan-sim-lock-dialog.h
create mode 100644 panels/wwan/cc-wwan-sim-lock-dialog.ui
create mode 100644 panels/wwan/gnome-wwan-panel.desktop.in.in
create mode 100644 panels/wwan/meson.build
create mode 100644 panels/wwan/wwan.gresource.xml
diff --git a/meson.build b/meson.build
index 900216962..75487603b 100644
--- a/meson.build
+++ b/meson.build
@@ -228,6 +228,10 @@ config_h.set('BUILD_NETWORK', host_is_linux,
description: 'Define to 1 to build the Network panel')
config_h.set('HAVE_NETWORK_MANAGER', host_is_linux,
description: 'Define to 1 if NetworkManager is available')
+config_h.set('BUILD_WWAN', host_is_linux,
+ description: 'Define to 1 to build the WWan panel')
+config_h.set('HAVE_WWAN', host_is_linux,
+ description: 'Define to 1 if WWan is available')
if host_is_linux_not_s390
# gnome-bluetooth
diff --git a/panels/meson.build b/panels/meson.build
index 2f4fdc5e3..9b9fc34f4 100644
--- a/panels/meson.build
+++ b/panels/meson.build
@@ -26,7 +26,8 @@ panels = [
'sound',
'universal-access',
'usage',
- 'user-accounts'
+ 'user-accounts',
+ 'wwan',
]
if host_is_linux
diff --git a/panels/wwan/cc-wwan-apn-dialog.c b/panels/wwan/cc-wwan-apn-dialog.c
new file mode 100644
index 000000000..bc5fde283
--- /dev/null
+++ b/panels/wwan/cc-wwan-apn-dialog.c
@@ -0,0 +1,424 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-apn-dialog.c
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wwan-apn-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "cc-wwan-device.h"
+#include "cc-wwan-data.h"
+#include "list-box-helper.h"
+#include "cc-wwan-apn-dialog.h"
+#include "cc-wwan-resources.h"
+
+/**
+ * @short_description: Dialog to manage Internet Access Points
+ */
+
+struct _CcWwanApnDialog
+{
+ GtkDialog parent_instance;
+
+ GtkButton *add_button;
+ GtkButton *back_button;
+ GtkButton *save_button;
+ GtkEntry *apn_entry;
+ GtkEntry *name_entry;
+ GtkEntry *password_entry;
+ GtkEntry *username_entry;
+ GtkGrid *apn_edit_view;
+ GtkListBox *apn_list;
+ GtkRadioButton *apn_radio_button;
+ GtkScrolledWindow *apn_list_view;
+ GtkStack *apn_settings_stack;
+
+ CcWwanData *wwan_data;
+ CcWwanDataApn *apn_to_save; /* The APN currently being edited */
+ CcWwanDevice *device;
+
+ gboolean enable_data;
+ gboolean enable_roaming;
+};
+
+G_DEFINE_TYPE (CcWwanApnDialog, cc_wwan_apn_dialog, GTK_TYPE_DIALOG)
+
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+#define CC_TYPE_WWAN_APN_ROW (cc_wwan_apn_row_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanApnRow, cc_wwan_apn_row, CC, WWAN_APN_ROW, GtkListBoxRow)
+
+struct _CcWwanApnRow
+{
+ GtkListBoxRow parent_instance;
+ GtkRadioButton *radio_button;
+ CcWwanDataApn *apn;
+};
+
+G_DEFINE_TYPE (CcWwanApnRow, cc_wwan_apn_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_wwan_apn_row_finalize (GObject *object)
+{
+ CcWwanApnRow *row = (CcWwanApnRow *)object;
+
+ g_clear_object (&row->apn);
+
+ G_OBJECT_CLASS (cc_wwan_apn_row_parent_class)->finalize (object);
+}
+
+static void
+cc_wwan_apn_row_class_init (CcWwanApnRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = cc_wwan_apn_row_finalize;
+}
+
+static void
+cc_wwan_apn_row_init (CcWwanApnRow *row)
+{
+}
+
+static void
+cc_wwan_apn_back_clicked_cb (CcWwanApnDialog *self)
+{
+ GtkWidget *view;
+
+ view = gtk_stack_get_visible_child (self->apn_settings_stack);
+
+ if (view == GTK_WIDGET (self->apn_edit_view))
+ {
+ gtk_widget_hide (GTK_WIDGET (self->save_button));
+ gtk_widget_show (GTK_WIDGET (self->add_button));
+ gtk_stack_set_visible_child (self->apn_settings_stack,
+ GTK_WIDGET (self->apn_list_view));
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (self));
+ }
+}
+
+static void
+cc_wwan_apn_add_clicked_cb (CcWwanApnDialog *self)
+{
+ gtk_entry_set_text (self->name_entry, "");
+ gtk_entry_set_text (self->apn_entry, "");
+ gtk_entry_set_text (self->username_entry, "");
+ gtk_entry_set_text (self->password_entry, "");
+
+ gtk_widget_hide (GTK_WIDGET (self->add_button));
+ gtk_widget_show (GTK_WIDGET (self->save_button));
+ self->apn_to_save = NULL;
+ gtk_stack_set_visible_child (self->apn_settings_stack,
+ GTK_WIDGET (self->apn_edit_view));
+}
+
+static void
+cc_wwan_apn_save_clicked_cb (CcWwanApnDialog *self)
+{
+ const gchar *name, *apn_name;
+ CcWwanDataApn *apn;
+
+ apn = self->apn_to_save;
+ self->apn_to_save = NULL;
+
+ name = gtk_entry_get_text (self->name_entry);
+ apn_name = gtk_entry_get_text (self->apn_entry);
+
+ if (!apn)
+ apn = cc_wwan_data_apn_new ();
+
+ cc_wwan_data_apn_set_name (apn, name);
+ cc_wwan_data_apn_set_apn (apn, apn_name);
+ cc_wwan_data_apn_set_username (apn, gtk_entry_get_text (self->username_entry));
+ cc_wwan_data_apn_set_password (apn, gtk_entry_get_text (self->password_entry));
+
+ cc_wwan_data_save_apn (self->wwan_data, apn, NULL, NULL, NULL);
+
+ gtk_widget_hide (GTK_WIDGET (self->save_button));
+ gtk_stack_set_visible_child (self->apn_settings_stack,
+ GTK_WIDGET (self->apn_list_view));
+}
+
+static void
+cc_wwan_apn_entry_changed_cb (CcWwanApnDialog *self)
+{
+ GtkWidget *widget;
+ const gchar *str;
+ gboolean valid_name, valid_apn;
+
+ widget = GTK_WIDGET (self->name_entry);
+ str = gtk_entry_get_text (self->name_entry);
+ valid_name = str && *str;
+
+ if (valid_name)
+ gtk_style_context_remove_class (gtk_widget_get_style_context (widget), "error");
+ else
+ gtk_style_context_add_class (gtk_widget_get_style_context (widget), "error");
+
+ widget = GTK_WIDGET (self->apn_entry);
+ str = gtk_entry_get_text (self->apn_entry);
+ valid_apn = str && *str;
+
+ if (valid_apn)
+ gtk_style_context_remove_class (gtk_widget_get_style_context (widget), "error");
+ else
+ gtk_style_context_add_class (gtk_widget_get_style_context (widget), "error");
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->save_button), valid_name && valid_apn);
+}
+
+static void
+cc_wwan_apn_activated_cb (CcWwanApnDialog *self,
+ CcWwanApnRow *row)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row->radio_button), TRUE);
+}
+
+static void
+cc_wwan_apn_changed_cb (CcWwanApnDialog *self,
+ GtkWidget *widget)
+{
+ CcWwanApnRow *row;
+
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
+ return;
+
+ widget = gtk_widget_get_ancestor (widget, CC_TYPE_WWAN_APN_ROW);
+ row = CC_WWAN_APN_ROW (widget);
+
+ if (cc_wwan_data_set_default_apn (self->wwan_data, row->apn))
+ cc_wwan_data_save_settings (self->wwan_data, NULL, NULL, NULL);
+}
+
+static void
+cc_wwan_apn_edit_clicked_cb (CcWwanApnDialog *self,
+ GtkButton *button)
+{
+ CcWwanDataApn *apn;
+ CcWwanApnRow *row;
+ GtkWidget *widget;
+
+ widget = gtk_widget_get_ancestor (GTK_WIDGET (button), CC_TYPE_WWAN_APN_ROW);
+ row = CC_WWAN_APN_ROW (widget);
+ apn = row->apn;
+ self->apn_to_save = apn;
+
+ gtk_widget_show (GTK_WIDGET (self->save_button));
+ gtk_widget_hide (GTK_WIDGET (self->add_button));
+
+ gtk_entry_set_text (self->name_entry, cc_wwan_data_apn_get_name (apn));
+ gtk_entry_set_text (self->apn_entry, cc_wwan_data_apn_get_apn (apn));
+ gtk_entry_set_text (self->username_entry, cc_wwan_data_apn_get_username (apn));
+ gtk_entry_set_text (self->password_entry, cc_wwan_data_apn_get_password (apn));
+
+ gtk_stack_set_visible_child (self->apn_settings_stack,
+ GTK_WIDGET (self->apn_edit_view));
+}
+
+static GtkWidget *
+cc_wwan_apn_dialog_row_new (CcWwanDataApn *apn,
+ CcWwanApnDialog *self)
+{
+ CcWwanApnRow *row;
+ GtkWidget *grid, *name_label, *apn_label, *radio, *edit_button;
+ GtkStyleContext *context;
+
+ row = g_object_new (CC_TYPE_WWAN_APN_ROW, NULL);
+
+ grid = g_object_new (GTK_TYPE_GRID,
+ "margin-top", 6,
+ "margin-bottom", 6,
+ "margin-start", 6,
+ "margin-end", 6,
+ NULL);
+
+ radio = gtk_radio_button_new_from_widget (self->apn_radio_button);
+ row->radio_button = GTK_RADIO_BUTTON (radio);
+ gtk_widget_set_margin_end (radio, 12);
+ gtk_grid_attach (GTK_GRID (grid), radio, 0, 0, 1, 2);
+ row->apn = g_object_ref (apn);
+
+ if (cc_wwan_data_get_default_apn (self->wwan_data) == apn)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE);
+ g_signal_connect_object (radio, "toggled",
+ G_CALLBACK (cc_wwan_apn_changed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ name_label = gtk_label_new (cc_wwan_data_apn_get_name (apn));
+ gtk_widget_set_halign (name_label, GTK_ALIGN_START);
+ gtk_widget_set_hexpand (name_label, TRUE);
+ gtk_grid_attach (GTK_GRID (grid), name_label, 1, 0, 1, 1);
+
+ apn_label = gtk_label_new (cc_wwan_data_apn_get_apn (apn));
+ gtk_widget_set_halign (apn_label, GTK_ALIGN_START);
+ context = gtk_widget_get_style_context (apn_label);
+ gtk_style_context_add_class (context, "dim-label");
+ gtk_grid_attach (GTK_GRID (grid), apn_label, 1, 1, 1, 1);
+
+ edit_button = gtk_button_new_from_icon_name ("emblem-system-symbolic",
+ GTK_ICON_SIZE_BUTTON);
+ g_signal_connect_object (edit_button, "clicked",
+ G_CALLBACK (cc_wwan_apn_edit_clicked_cb),
+ self, G_CONNECT_SWAPPED);
+ gtk_grid_attach (GTK_GRID (grid), edit_button, 2, 0, 1, 2);
+
+ gtk_container_add (GTK_CONTAINER (row), grid);
+ gtk_widget_show_all (GTK_WIDGET (row));
+
+ return GTK_WIDGET (row);
+}
+
+static void
+cc_wwan_apn_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWwanApnDialog *self = (CcWwanApnDialog *)object;
+
+ switch (prop_id)
+ {
+ case PROP_DEVICE:
+ self->device = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wwan_apn_dialog_constructed (GObject *object)
+{
+ CcWwanApnDialog *self = (CcWwanApnDialog *)object;
+
+ G_OBJECT_CLASS (cc_wwan_apn_dialog_parent_class)->constructed (object);
+
+ self->wwan_data = cc_wwan_device_get_data (self->device);
+
+ gtk_list_box_bind_model (self->apn_list,
+ cc_wwan_data_get_apn_list (self->wwan_data),
+ (GtkListBoxCreateWidgetFunc)cc_wwan_apn_dialog_row_new,
+ self, NULL);
+}
+
+static void
+cc_wwan_apn_dialog_dispose (GObject *object)
+{
+ CcWwanApnDialog *self = (CcWwanApnDialog *)object;
+
+ g_clear_object (&self->device);
+
+ G_OBJECT_CLASS (cc_wwan_apn_dialog_parent_class)->dispose (object);
+}
+
+
+static void
+cc_wwan_apn_dialog_show (GtkWidget *widget)
+{
+ CcWwanApnDialog *self = (CcWwanApnDialog *)widget;
+
+ gtk_widget_show (GTK_WIDGET (self->add_button));
+ gtk_widget_hide (GTK_WIDGET (self->save_button));
+ gtk_stack_set_visible_child (self->apn_settings_stack,
+ GTK_WIDGET (self->apn_list_view));
+
+ GTK_WIDGET_CLASS (cc_wwan_apn_dialog_parent_class)->show (widget);
+}
+
+static void
+cc_wwan_apn_dialog_class_init (CcWwanApnDialogClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = cc_wwan_apn_dialog_set_property;
+ object_class->constructed = cc_wwan_apn_dialog_constructed;
+ object_class->dispose = cc_wwan_apn_dialog_dispose;
+
+ widget_class->show = cc_wwan_apn_dialog_show;
+
+ properties[PROP_DEVICE] =
+ g_param_spec_object ("device",
+ "Device",
+ "The WWAN Device",
+ CC_TYPE_WWAN_DEVICE,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/wwan/cc-wwan-apn-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, add_button);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_edit_view);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_list);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_list_view);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_radio_button);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_settings_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, back_button);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, name_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, password_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, save_button);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, username_entry);
+
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_back_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_add_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_save_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_entry_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_activated_cb);
+}
+
+static void
+cc_wwan_apn_dialog_init (CcWwanApnDialog *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcWwanApnDialog *
+cc_wwan_apn_dialog_new (GtkWindow *parent_window,
+ CcWwanDevice *device)
+{
+ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+ return g_object_new (CC_TYPE_WWAN_APN_DIALOG,
+ "transient-for", parent_window,
+ "use-header-bar", 1,
+ "device", device,
+ NULL);
+}
diff --git a/panels/wwan/cc-wwan-apn-dialog.h b/panels/wwan/cc-wwan-apn-dialog.h
new file mode 100644
index 000000000..0e9885836
--- /dev/null
+++ b/panels/wwan/cc-wwan-apn-dialog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-apn-dialog.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <handy.h>
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_APN_DIALOG (cc_wwan_apn_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanApnDialog, cc_wwan_apn_dialog, CC, WWAN_APN_DIALOG, GtkDialog)
+
+CcWwanApnDialog *cc_wwan_apn_dialog_new (GtkWindow *parent_window,
+ CcWwanDevice *device);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-apn-dialog.ui b/panels/wwan/cc-wwan-apn-dialog.ui
new file mode 100644
index 000000000..fb8432bc6
--- /dev/null
+++ b/panels/wwan/cc-wwan-apn-dialog.ui
@@ -0,0 +1,249 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcWwanApnDialog" parent="GtkDialog">
+ <property name="default-height">480</property>
+ <property name="default-width">360</property>
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+ <child type="titlebar">
+ <object class="GtkHeaderBar">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes">Access Points</property>
+
+ <!-- Back button -->
+ <child>
+ <object class="GtkButton" id="back_button">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <signal name="clicked" handler="cc_wwan_apn_back_clicked_cb" swapped="yes" />
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Back</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">go-previous-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+
+ <!-- Add button -->
+ <child>
+ <object class="GtkButton" id="add_button">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <signal name="clicked" handler="cc_wwan_apn_add_clicked_cb" swapped="yes" />
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Add</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">list-add-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+
+ <!-- Save button -->
+ <child>
+ <object class="GtkButton" id="save_button">
+ <property name="visible">0</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Save</property>
+ <signal name="clicked" handler="cc_wwan_apn_save_clicked_cb" swapped="yes" />
+ <style>
+ <class name="default" />
+ </style>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="border-width">0</property>
+ <property name="width-request">340</property>
+ <property name="height-request">360</property>
+
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">1</property>
+ <property name="margin-top">32</property>
+ <property name="margin-bottom">32</property>
+
+ <child>
+ <object class="GtkStack" id="apn_settings_stack">
+ <property name="visible">1</property>
+ <property name="transition-type">slide-left-right</property>
+
+ <!-- Access Point List -->
+ <child>
+ <object class="GtkScrolledWindow" id="apn_list_view">
+ <property name="visible">1</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <child>
+ <object class="GtkListBox" id="apn_list">
+ <property name="visible">1</property>
+ <property name="valign">start</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="cc_wwan_apn_activated_cb" swapped="yes" />
+ <style>
+ <class name="content" />
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkGrid" id="apn_edit_view">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="expand">1</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">12</property>
+
+ <!-- Name -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">Name</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <signal name="changed" handler="cc_wwan_apn_entry_changed_cb" swapped="yes" />
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <!-- APN -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">APN</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="apn_entry">
+ <property name="visible">1</property>
+ <property name="margin-bottom">12</property>
+ <signal name="changed" handler="cc_wwan_apn_entry_changed_cb" swapped="yes" />
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+
+ <!-- Username -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">Username</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="username_entry">
+ <property name="visible">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+
+ <!-- Password -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">Passsword</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="password_entry">
+ <property name="visible">1</property>
+ <property name="margin-bottom">12</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+
+ </object> <!-- ./GtkStack apn_settings_stack -->
+ </child>
+ </object>
+ </child>
+ </object>
+ </child> <!-- ./internal-child -->
+
+ </template>
+
+ <!-- A simple hack to create a radio button group -->
+ <object class="GtkRadioButton" id="apn_radio_button" />
+</interface>
diff --git a/panels/wwan/cc-wwan-data.c b/panels/wwan/cc-wwan-data.c
new file mode 100644
index 000000000..0be8f3403
--- /dev/null
+++ b/panels/wwan/cc-wwan-data.c
@@ -0,0 +1,1446 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-data.c
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wwan-data"
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <glib/gi18n.h>
+#include <nma-mobile-providers.h>
+
+#include "cc-wwan-data.h"
+
+/**
+ * @short_description: Device Internet Data Object
+ * @include: "cc-wwan-device-data.h"
+ *
+ * #CcWwanData represents the data object of the given
+ * #CcWwanDevice. Please note that while #CcWWanDevice
+ * is bound to the hardware device, #CcWwanData may also
+ * depend on the inserted SIM (if supported). So the state
+ * of #CcWwanData changes when SIM is changed.
+ */
+
+/* Priority for connections. larger the number, lower the priority */
+#define CC_WWAN_DNS_PRIORITY_LOW (20)
+#define CC_WWAN_DNS_PRIORITY_HIGH (15)
+
+/* These are to be set as route metric */
+#define CC_WWAN_ROUTE_PRIORITY_LOW (1050)
+#define CC_WWAN_ROUTE_PRIORITY_HIGH (1040)
+
+struct _CcWwanData
+{
+ GObject parent_instance;
+
+ MMObject *mm_object;
+ MMModem *modem;
+ MMSim *sim;
+ gchar *sim_id;
+
+ gchar *operator_code; /* MCCMNC */
+ GError *error;
+
+ NMClient *nm_client;
+ NMDevice *nm_device;
+ NMAMobileProvidersDatabase *apn_db;
+ NMAMobileProvider *apn_provider;
+ CcWwanDataApn *default_apn;
+ CcWwanDataApn *old_default_apn;
+ GListStore *apn_list;
+ NMActiveConnection *active_connection;
+
+ gint priority;
+ gboolean data_enabled; /* autoconnect enabled */
+ gboolean home_only; /* Data roaming */
+};
+
+G_DEFINE_TYPE (CcWwanData, cc_wwan_data, G_TYPE_OBJECT)
+
+/*
+ * Default Access Point Settings Logic:
+ * For a provided SIM, all the APNs available from NetworkManager
+ * that matches the given SIM identifier (ICCID, available via
+ * mm_sim_get_identifier() or similar gdbus API) is loaded for
+ * the Device (In NetworkManager, it is saved as ‘sim-id’, if
+ * present). At a time, only one connection will be bound to
+ * a device. If there are more than one match, the item with
+ * the highest ‘route-metric’ is taken. If more matches are
+ * still available, the first item is chosen.
+ *
+ * Populating All available APNs:
+ * All Possible APNs for the given sim are populated the following
+ * way (A list of all the following avoiding duplicates)
+ * 1. The above mentioned “Default Access Point Settings Logic”
+ * 2. Get All saved Network Manager connections with the
+ * provided MCCMNC of the given SIM
+ * 3. Get All possible APNs for the MCCMNC from mobile-provider-info
+ *
+ * Testing if data is enabled:
+ * Check if any of the items from step 1 have ‘autoconnect’ set
+ *
+ * Checking/Setting current SIM for data (in case of multiple SIM):
+ * Since other networks (like wifi, ethernet) should have higher
+ * priorities we use a negative number for priority.
+ * 1. All APNs by default have priority CC_WWAN_APN_PRIORITY_LOW
+ * 2. APN of selected SIM for active data have priority of
+ * CC_WWAN_APN_PRIORITY_HIGH
+ *
+ * XXX: Since users may create custom APNs via nmtui or like tools
+ * we may have to check if there are some inconsistencies with APNs
+ * available in NetworkManager, and ask user if they have to reset
+ * the APNs that have invalid settings (basically, we care only APNs
+ * that are set to have ‘autoconnect’ enabled, and all we need is to
+ * disable autoconnect). We won’t interfere CDMA/EVDO networks.
+ */
+struct _CcWwanDataApn {
+ GObject parent_instance;
+
+ /* Set if the APN is from the mobile-provider-info database */
+ NMAMobileAccessMethod *access_method;
+
+ /* Set if the APN is saved in NetworkManager */
+ NMConnection *nm_connection;
+ NMRemoteConnection *remote_connection;
+
+ gboolean modified;
+};
+
+G_DEFINE_TYPE (CcWwanDataApn, cc_wwan_data_apn, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_ERROR,
+ PROP_ENABLED,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+wwan_data_apn_reset (CcWwanDataApn *apn)
+{
+ if (!apn)
+ return;
+
+ g_clear_object (&apn->nm_connection);
+ g_clear_object (&apn->remote_connection);
+}
+
+static NMConnection *
+wwan_data_get_nm_connection (CcWwanDataApn *apn)
+{
+ NMConnection *connection;
+ NMSetting *setting;
+ g_autofree gchar *uuid = NULL;
+
+ if (apn->nm_connection)
+ return apn->nm_connection;
+
+ if (apn->remote_connection)
+ return NM_CONNECTION (apn->remote_connection);
+
+ connection = nm_simple_connection_new ();
+ apn->nm_connection = connection;
+
+ setting = nm_setting_connection_new ();
+ uuid = nm_utils_uuid_generate ();
+ g_object_set (setting,
+ NM_SETTING_CONNECTION_UUID, uuid,
+ NM_SETTING_CONNECTION_TYPE, NM_SETTING_GSM_SETTING_NAME,
+ NULL);
+ nm_connection_add_setting (connection, setting);
+
+ setting = nm_setting_serial_new ();
+ nm_connection_add_setting (connection, setting);
+
+ setting = nm_setting_ip4_config_new ();
+ g_object_set (setting, NM_SETTING_IP_CONFIG_METHOD, "auto", NULL);
+ nm_connection_add_setting (connection, setting);
+
+ nm_connection_add_setting (connection, nm_setting_gsm_new ());
+ nm_connection_add_setting (connection, nm_setting_ppp_new ());
+
+ return apn->nm_connection;
+}
+
+static gboolean
+wwan_data_apn_are_same (NMRemoteConnection *remote_connection,
+ NMAMobileAccessMethod *access_method)
+{
+ NMConnection *connection;
+ NMSetting *setting;
+
+ if (!remote_connection)
+ return FALSE;
+
+ connection = NM_CONNECTION (remote_connection);
+ setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+
+ if (g_strcmp0 (nma_mobile_access_method_get_3gpp_apn (access_method),
+ nm_setting_gsm_get_apn (NM_SETTING_GSM (setting))) != 0)
+ return FALSE;
+
+ if (g_strcmp0 (nma_mobile_access_method_get_username (access_method),
+ nm_setting_gsm_get_username (NM_SETTING_GSM (setting))) != 0)
+ return FALSE;
+
+ if (g_strcmp0 (nma_mobile_access_method_get_password (access_method),
+ nm_setting_gsm_get_password (NM_SETTING_GSM (setting))) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static CcWwanDataApn *
+wwan_data_find_matching_apn (CcWwanData *self,
+ NMAMobileAccessMethod *access_method)
+{
+ CcWwanDataApn *apn;
+ guint i, n_items;
+
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->apn_list));
+
+ for (i = 0; i < n_items; i++)
+ {
+ apn = g_list_model_get_item (G_LIST_MODEL (self->apn_list), i);
+
+ if (apn->access_method == access_method)
+ return apn;
+
+ if (wwan_data_apn_are_same (apn->remote_connection,
+ access_method))
+ return apn;
+
+ g_object_unref (apn);
+ }
+
+ return NULL;
+}
+
+static gboolean
+wwan_data_nma_method_is_mms (NMAMobileAccessMethod *method)
+{
+ const char *str;
+
+ str = nma_mobile_access_method_get_3gpp_apn (method);
+ if (str && strcasestr (str, "mms"))
+ return TRUE;
+
+ str = nma_mobile_access_method_get_name (method);
+ if (str && strcasestr (str, "mms"))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+wwan_data_update_apn_list_db (CcWwanData *self)
+{
+ GSList *apn_methods = NULL, *l;
+ g_autoptr(GError) error = NULL;
+ guint i = 0;
+
+ if (!self->sim || !self->operator_code)
+ return;
+
+ if (!self->apn_list)
+ self->apn_list = g_list_store_new (CC_TYPE_WWAN_DATA_APN);
+
+ if (!self->apn_db)
+ self->apn_db = nma_mobile_providers_database_new_sync (NULL, NULL, NULL, &error);
+
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ return;
+ }
+
+ if (!self->apn_provider)
+ self->apn_provider = nma_mobile_providers_database_lookup_3gpp_mcc_mnc (self->apn_db,
+ self->operator_code);
+
+ if (self->apn_provider)
+ apn_methods = nma_mobile_provider_get_methods (self->apn_provider);
+
+ for (l = apn_methods; l; l = l->next, i++)
+ {
+ g_autoptr(CcWwanDataApn) apn = NULL;
+
+ /* We don’t list MMS APNs */
+ if (wwan_data_nma_method_is_mms (l->data))
+ continue;
+
+ apn = wwan_data_find_matching_apn (self, l->data);
+
+ /* Prepend the item in order */
+ if (!apn)
+ {
+ apn = cc_wwan_data_apn_new ();
+ g_list_store_insert (self->apn_list, i, apn);
+ }
+
+ apn->access_method = l->data;
+ }
+}
+
+static void
+wwan_data_update_apn_list (CcWwanData *self)
+{
+ const GPtrArray *nm_connections;
+ guint i;
+
+ if (self->apn_list || !self->sim)
+ return;
+
+ if (!self->apn_list)
+ self->apn_list = g_list_store_new (CC_TYPE_WWAN_DATA_APN);
+
+ if (self->nm_device)
+ {
+ nm_connections = nm_device_get_available_connections (self->nm_device);
+
+ for (i = 0; i < nm_connections->len; i++)
+ {
+ g_autoptr(CcWwanDataApn) apn = NULL;
+
+ apn = cc_wwan_data_apn_new ();
+ apn->remote_connection = g_object_ref (nm_connections->pdata[i]);
+ g_list_store_append (self->apn_list, apn);
+
+ /* Load the default APN */
+ if (!self->default_apn && self->sim_id)
+ {
+ NMSettingConnection *connection_setting;
+ NMSettingIPConfig *ip_setting;
+ NMSettingGsm *setting;
+ NMConnection *connection;
+ const gchar *sim_id;
+
+ connection = NM_CONNECTION (apn->remote_connection);
+ setting = nm_connection_get_setting_gsm (connection);
+ connection_setting = nm_connection_get_setting_connection (connection);
+ sim_id = nm_setting_gsm_get_sim_id (setting);
+
+ if (sim_id && *sim_id && g_str_equal (sim_id, self->sim_id))
+ {
+ self->default_apn = apn;
+ self->home_only = nm_setting_gsm_get_home_only (setting);
+ self->data_enabled = nm_setting_connection_get_autoconnect (connection_setting);
+
+ /* If any of the APN has a high priority, the device have high priority */
+ ip_setting = nm_connection_get_setting_ip4_config (connection);
+ if (nm_setting_ip_config_get_route_metric (ip_setting) == CC_WWAN_ROUTE_PRIORITY_HIGH)
+ self->priority = CC_WWAN_APN_PRIORITY_HIGH;
+ }
+ }
+ }
+ }
+}
+
+static void
+wwan_device_state_changed_cb (CcWwanData *self)
+{
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLED]);
+}
+
+static void
+cc_wwan_data_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcWwanData *self = (CcWwanData *)object;
+
+ switch (prop_id)
+ {
+ case PROP_ERROR:
+ g_value_set_boolean (value, self->error != NULL);
+ break;
+
+ case PROP_ENABLED:
+ g_value_set_boolean (value, cc_wwan_data_get_enabled (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wwan_data_dispose (GObject *object)
+{
+ CcWwanData *self = (CcWwanData *)object;
+
+ g_clear_pointer (&self->sim_id, g_free);
+ g_clear_pointer (&self->operator_code, g_free);
+ g_clear_error (&self->error);
+ g_clear_object (&self->apn_list);
+ g_clear_object (&self->modem);
+ g_clear_object (&self->mm_object);
+ g_clear_object (&self->nm_client);
+ g_clear_object (&self->active_connection);
+ g_clear_object (&self->apn_db);
+
+ G_OBJECT_CLASS (cc_wwan_data_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_data_class_init (CcWwanDataClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = cc_wwan_data_get_property;
+ object_class->dispose = cc_wwan_data_dispose;
+
+ properties[PROP_ERROR] =
+ g_param_spec_boolean ("error",
+ "Error",
+ "Set if some Error occurs",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_ENABLED] =
+ g_param_spec_boolean ("enabled",
+ "Enabled",
+ "Get if the data is enabled",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+cc_wwan_data_init (CcWwanData *self)
+{
+ self->home_only = TRUE;
+ self->priority = CC_WWAN_APN_PRIORITY_LOW;
+}
+
+/**
+ * cc_wwan_data_new:
+ * @mm_object: An #MMObject
+ * @nm_client: An #NMClient
+ *
+ * Create a new device data representing the given
+ * @mm_object. If @mm_object isn’t a 3G/CDMA/LTE
+ * modem, %NULL will be returned
+ *
+ * Returns: A #CcWwanData or %NULL.
+ */
+CcWwanData *
+cc_wwan_data_new (MMObject *mm_object,
+ NMClient *nm_client)
+{
+ CcWwanData *self;
+ NMDevice *nm_device = NULL;
+ g_autoptr(MMModem) modem = NULL;
+ NMDeviceModemCapabilities capabilities = 0;
+
+ g_return_val_if_fail (MM_IS_OBJECT (mm_object), NULL);
+ g_return_val_if_fail (NM_CLIENT (nm_client), NULL);
+
+ modem = mm_object_get_modem (mm_object);
+
+ if (modem)
+ nm_device = nm_client_get_device_by_iface (nm_client,
+ mm_modem_get_primary_port (modem));
+
+ if (NM_IS_DEVICE_MODEM (nm_device))
+ capabilities = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (nm_device));
+
+ if (!(capabilities & (NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS
+ | NM_DEVICE_MODEM_CAPABILITY_LTE)))
+ return NULL;
+
+ self = g_object_new (CC_TYPE_WWAN_DATA, NULL);
+
+ self->nm_client = g_object_ref (nm_client);
+ self->mm_object = g_object_ref (mm_object);
+ self->modem = g_steal_pointer (&modem);
+ self->sim = mm_modem_get_sim_sync (self->modem, NULL, NULL);
+ self->sim_id = mm_sim_dup_identifier (self->sim);
+ self->operator_code = mm_sim_dup_operator_identifier (self->sim);
+ self->nm_device = g_object_ref (nm_device);
+ self->active_connection = nm_device_get_active_connection (nm_device);
+
+ if (!self->operator_code)
+ {
+ MMModem3gpp *modem_3gpp;
+
+ modem_3gpp = mm_object_peek_modem_3gpp (mm_object);
+ if (modem_3gpp)
+ self->operator_code = mm_modem_3gpp_dup_operator_code (modem_3gpp);
+ }
+
+ if (self->active_connection)
+ g_object_ref (self->active_connection);
+
+ g_signal_connect_object (self->nm_device, "notify::state",
+ G_CALLBACK (wwan_device_state_changed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ wwan_data_update_apn_list (self);
+ wwan_data_update_apn_list_db (self);
+
+ return self;
+}
+
+GError *
+cc_wwan_data_get_error (CcWwanData *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL);
+
+ return self->error;
+}
+
+const gchar *
+cc_wwan_data_get_simple_html_error (CcWwanData *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL);
+
+ if (!self->error)
+ return NULL;
+
+ if (g_error_matches (self->error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return _("Operation Cancelled");
+
+ if (g_error_matches (self->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED))
+ return _("<b>Error:</b> Access denied changing settings");
+
+ if (self->error->domain == MM_MOBILE_EQUIPMENT_ERROR)
+ return _("<b>Error:</b> Mobile Equipment Error");
+
+ return NULL;
+}
+
+GListModel *
+cc_wwan_data_get_apn_list (CcWwanData *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL);
+
+ if (!self->apn_list)
+ wwan_data_update_apn_list (self);
+
+ return G_LIST_MODEL (self->apn_list);
+}
+
+static gboolean
+wwan_data_apn_is_new (CcWwanDataApn *apn)
+{
+ return apn->remote_connection == NULL;
+}
+
+static void
+wwan_data_update_apn (CcWwanData *self,
+ CcWwanDataApn *apn,
+ NMConnection *connection)
+{
+ NMSetting *setting;
+ const gchar *name, *username, *password, *apn_name;
+ gint dns_priority, route_metric;
+
+ setting = NM_SETTING (nm_connection_get_setting_connection (connection));
+
+ g_object_set (setting,
+ NM_SETTING_CONNECTION_AUTOCONNECT, self->data_enabled,
+ NULL);
+
+ setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+
+ g_object_set (setting,
+ NM_SETTING_GSM_HOME_ONLY, self->home_only,
+ NULL);
+
+ setting = NM_SETTING (nm_connection_get_setting_ip4_config (connection));
+ if (self->priority == CC_WWAN_APN_PRIORITY_HIGH &&
+ self->default_apn == apn)
+ {
+ dns_priority = CC_WWAN_DNS_PRIORITY_HIGH;
+ route_metric = CC_WWAN_ROUTE_PRIORITY_HIGH;
+ }
+ else
+ {
+ dns_priority = CC_WWAN_DNS_PRIORITY_LOW;
+ route_metric = CC_WWAN_ROUTE_PRIORITY_LOW;
+ }
+
+ g_object_set (setting,
+ NM_SETTING_IP_CONFIG_DNS_PRIORITY, dns_priority,
+ NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64)route_metric,
+ NULL);
+
+ if (apn->access_method && !apn->remote_connection)
+ {
+ name = nma_mobile_access_method_get_name (apn->access_method);
+ username = nma_mobile_access_method_get_username (apn->access_method);
+ password = nma_mobile_access_method_get_password (apn->access_method);
+ apn_name = nma_mobile_access_method_get_3gpp_apn (apn->access_method);
+ }
+ else
+ {
+ return;
+ }
+
+ setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+ g_object_set (setting,
+ NM_SETTING_GSM_USERNAME, username,
+ NM_SETTING_GSM_PASSWORD, password,
+ NM_SETTING_GSM_APN, apn_name,
+ NULL);
+
+ setting = NM_SETTING (nm_connection_get_setting_connection (connection));
+
+ g_object_set (setting,
+ NM_SETTING_CONNECTION_ID, name,
+ NULL);
+}
+
+static gint
+wwan_data_get_apn_index (CcWwanData *self,
+ CcWwanDataApn *apn)
+{
+ GListModel *model;
+ guint i, n_items;
+
+ model = G_LIST_MODEL (self->apn_list);
+ n_items = g_list_model_get_n_items (model);
+
+ for (i = 0; i < n_items; i++)
+ {
+ g_autoptr(CcWwanDataApn) cached_apn = NULL;
+
+ cached_apn = g_list_model_get_item (model, i);
+
+ if (apn == cached_apn)
+ return i;
+ }
+
+ return -1;
+}
+
+static void
+cc_wwan_data_connection_updated_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanData *self;
+ CcWwanDataApn *apn;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ self = g_task_get_source_object (G_TASK (task));
+ apn = g_task_get_task_data (G_TASK (task));
+
+ nm_remote_connection_commit_changes_finish (apn->remote_connection,
+ result, &error);
+ if (!error)
+ {
+ guint apn_index;
+ apn_index = wwan_data_get_apn_index (self, apn);
+
+ if (apn_index >= 0)
+ g_list_model_items_changed (G_LIST_MODEL (self->apn_list),
+ apn_index, 1, 1);
+ else
+ g_warning ("APN ‘%s’ not in APN list",
+ cc_wwan_data_apn_get_name (apn));
+
+ apn->modified = FALSE;
+ g_task_return_boolean (task, TRUE);
+ }
+ else
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+}
+
+static void
+cc_wwan_data_new_connection_added_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanData *self;
+ CcWwanDataApn *apn;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ self = g_task_get_source_object (G_TASK (task));
+ apn = g_task_get_task_data (G_TASK (task));
+ apn->remote_connection = nm_client_add_connection_finish (self->nm_client,
+ result, &error);
+ if (!error)
+ {
+ apn->modified = FALSE;
+
+ /* If APN has access method, it’s already on the list */
+ if (!apn->access_method)
+ {
+ g_list_store_append (self->apn_list, apn);
+ g_object_unref (apn);
+ }
+
+ g_task_return_pointer (task, apn, NULL);
+ }
+ else
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+}
+
+void
+cc_wwan_data_save_apn (CcWwanData *self,
+ CcWwanDataApn *apn,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ NMConnection *connection = NULL;
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (CC_IS_WWAN_DATA (self));
+ g_return_if_fail (CC_IS_WWAN_DATA_APN (apn));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, apn, NULL);
+
+ connection = wwan_data_get_nm_connection (apn);
+
+ /* If the item has a remote connection, it should already be saved.
+ * We should save it again only if it got modified */
+ if (apn->remote_connection && !apn->modified)
+ {
+ g_task_return_pointer (task, apn, NULL);
+ return;
+ }
+
+ wwan_data_update_apn (self, apn, connection);
+ if (wwan_data_apn_is_new (apn))
+ {
+ nm_client_add_connection_async (self->nm_client, apn->nm_connection,
+ TRUE, cancellable,
+ cc_wwan_data_new_connection_added_cb,
+ g_steal_pointer (&task));
+ }
+ else
+ {
+ nm_remote_connection_commit_changes_async (apn->remote_connection, TRUE,
+ cancellable,
+ cc_wwan_data_connection_updated_cb,
+ g_steal_pointer (&task));
+ }
+}
+
+CcWwanDataApn *
+cc_wwan_data_save_apn_finish (CcWwanData *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL);
+ g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+cc_wwan_data_activated_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanData *self;
+ NMActiveConnection *connection;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ self = g_task_get_source_object (G_TASK (task));
+ connection = nm_client_activate_connection_finish (self->nm_client,
+ result, &error);
+ if (connection)
+ {
+ g_set_object (&self->active_connection, connection);
+ g_task_return_boolean (task, TRUE);
+ }
+ else
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+
+ if (error)
+ g_warning ("Error: %s", error->message);
+}
+
+static void
+cc_wwan_data_settings_saved_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanData *self;
+ GCancellable *cancellable;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ self = g_task_get_source_object (G_TASK (task));
+ cancellable = g_task_get_cancellable (G_TASK (task));
+
+ if (!cc_wwan_data_save_apn_finish (self, result, &error))
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ self->default_apn->modified = FALSE;
+
+ if (self->data_enabled)
+ {
+ nm_client_activate_connection_async (self->nm_client,
+ NM_CONNECTION (self->default_apn->remote_connection),
+ self->nm_device,
+ NULL, cancellable,
+ cc_wwan_data_activated_cb,
+ g_steal_pointer (&task));
+ }
+ else
+ {
+ if (nm_device_disconnect (self->nm_device, cancellable, &error))
+ {
+ g_clear_object (&self->active_connection);
+ g_task_return_boolean (task, TRUE);
+ }
+ else
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ }
+}
+
+/**
+ * cc_wwan_data_save_settings:
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: a #GAsyncReadyCallback, or %NULL
+ * @user_data: closure data for @callback
+ *
+ * Save default settings to disk and apply changes.
+ * If the default APN has data enabled, the data is
+ * activated after the settings are saved.
+ *
+ * It’s a programmer error to call this function without
+ * a default APN set.
+ * Finish with cc_wwan_data_save_settings_finish().
+ */
+void
+cc_wwan_data_save_settings (CcWwanData *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ NMConnection *connection;
+ NMSetting *setting;
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (CC_IS_WWAN_DATA (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (self->default_apn != NULL);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ /* Reset old settings to default value */
+ if (self->old_default_apn && self->old_default_apn->remote_connection)
+ {
+ connection = NM_CONNECTION (self->old_default_apn->remote_connection);
+
+ setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+ g_object_set (G_OBJECT (setting),
+ NM_SETTING_GSM_HOME_ONLY, TRUE,
+ NM_SETTING_GSM_SIM_ID, NULL,
+ NULL);
+
+ setting = NM_SETTING (nm_connection_get_setting_ip4_config (connection));
+ g_object_set (setting,
+ NM_SETTING_IP_CONFIG_DNS_PRIORITY, CC_WWAN_DNS_PRIORITY_LOW,
+ NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64)CC_WWAN_ROUTE_PRIORITY_LOW,
+ NULL);
+
+ setting = NM_SETTING (nm_connection_get_setting_connection (connection));
+ g_object_set (G_OBJECT (setting),
+ NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
+ NULL);
+
+ nm_remote_connection_commit_changes (NM_REMOTE_CONNECTION (connection),
+ TRUE, cancellable, NULL);
+ self->old_default_apn->modified = FALSE;
+ self->old_default_apn = NULL;
+ }
+
+ self->default_apn->modified = TRUE;
+ connection = wwan_data_get_nm_connection (self->default_apn);
+
+ setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+ g_object_set (G_OBJECT (setting),
+ NM_SETTING_GSM_HOME_ONLY, self->home_only,
+ NM_SETTING_GSM_SIM_ID, self->sim_id,
+ NULL);
+
+ cc_wwan_data_save_apn (self, self->default_apn, cancellable,
+ cc_wwan_data_settings_saved_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_data_save_settings_finish (CcWwanData *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+gboolean
+cc_wwan_data_delete_apn (CcWwanData *self,
+ CcWwanDataApn *apn,
+ GCancellable *cancellable,
+ GError **error)
+{
+ NMRemoteConnection *connection = NULL;
+ gboolean ret = FALSE;
+ gint apn_index;
+
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE);
+ g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), FALSE);
+ g_return_val_if_fail (error != NULL, FALSE);
+
+ apn_index = wwan_data_get_apn_index (self, apn);
+ if (apn_index == -1)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "APN not found for the connection");
+ return FALSE;
+ }
+
+ connection = g_steal_pointer (&apn->remote_connection);
+ wwan_data_apn_reset (apn);
+
+ if (connection)
+ ret = nm_remote_connection_delete (connection, cancellable, error);
+
+ if (!ret)
+ {
+ apn->remote_connection = connection;
+ *error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Deleting APN from NetworkManager failed");
+ return ret;
+ }
+
+ g_object_unref (connection);
+
+ /* We remove the item only if it's not in the mobile provider database */
+ if (!apn->access_method)
+ {
+ if (self->default_apn == apn)
+ self->default_apn = NULL;
+
+ g_list_store_remove (self->apn_list, apn_index);
+
+ return TRUE;
+ }
+
+ *error = g_error_new (G_IO_ERROR, G_IO_ERROR_READ_ONLY,
+ "Deleting APN from NetworkManager failed");
+ return FALSE;
+}
+
+CcWwanDataApn *
+cc_wwan_data_get_default_apn (CcWwanData *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL);
+
+ return self->default_apn;
+}
+
+gboolean
+cc_wwan_data_set_default_apn (CcWwanData *self,
+ CcWwanDataApn *apn)
+{
+ NMConnection *connection;
+ NMSetting *setting;
+
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE);
+ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), FALSE);
+ g_return_val_if_fail (self->sim_id != NULL, FALSE);
+
+ if (self->default_apn == apn)
+ return FALSE;
+
+ /*
+ * APNs are bound to the SIM, not the modem device.
+ * This will let the APN work if the same SIM inserted
+ * in a different device, and not enable data if a
+ * different SIM is inserted to the modem.
+ */
+ apn->modified = TRUE;
+ self->old_default_apn = self->default_apn;
+ self->default_apn = apn;
+ connection = wwan_data_get_nm_connection (apn);
+ setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+ g_object_set (G_OBJECT (setting),
+ NM_SETTING_GSM_SIM_ID, self->sim_id, NULL);
+
+ return TRUE;
+}
+
+gboolean
+cc_wwan_data_get_enabled (CcWwanData *self)
+{
+ NMSettingConnection *setting;
+ NMConnection *connection;
+ NMDeviceState state;
+
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE);
+
+ state = nm_device_get_state (self->nm_device);
+
+ if (state == NM_DEVICE_STATE_DISCONNECTED ||
+ state == NM_DEVICE_STATE_DEACTIVATING)
+ if (nm_device_get_state_reason (self->nm_device) == NM_DEVICE_STATE_REASON_USER_REQUESTED)
+ return FALSE;
+
+ if (nm_device_get_active_connection (self->nm_device) != NULL)
+ return TRUE;
+
+ if (!self->default_apn || !self->default_apn->remote_connection)
+ return FALSE;
+
+ connection = NM_CONNECTION (self->default_apn->remote_connection);
+ setting = nm_connection_get_setting_connection (connection);
+
+ return nm_setting_connection_get_autoconnect (setting);
+}
+
+/**
+ * cc_wwan_data_set_enabled:
+ * @self: A #CcWwanData
+ * @enable_data: whether to enable data
+ *
+ * Enable data for the device. The settings is
+ * saved to disk only after a default APN is set.
+ *
+ * If the data is enabled, the device will automatically
+ * turn data on everytime the same SIM is available.
+ * The data set is bound to the SIM, not the modem device.
+ *
+ * Use @cc_wwan_data_save_apn() with the default APN
+ * to save the changes and really enable/disable data.
+ */
+void
+cc_wwan_data_set_enabled (CcWwanData *self,
+ gboolean enable_data)
+{
+ g_return_if_fail (CC_IS_WWAN_DATA (self));
+
+ self->data_enabled = !!enable_data;
+
+ if (self->default_apn)
+ self->default_apn->modified = TRUE;
+}
+
+gboolean
+cc_wwan_data_get_roaming_enabled (CcWwanData *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE);
+
+ if (!self->default_apn)
+ return FALSE;
+
+ return !self->home_only;
+}
+
+/**
+ * cc_wwan_data_apn_set_roaming_enabled:
+ * @self: A #CcWwanData
+ * @enable_roaming: whether to enable roaming or not
+ *
+ * Enable roaming for the device. The settings is
+ * saved to disk only after a default APN is set.
+ *
+ * Use @cc_wwan_data_save_apn() with the default APN
+ * to save the changes and really enable/disable data.
+ */
+void
+cc_wwan_data_set_roaming_enabled (CcWwanData *self,
+ gboolean enable_roaming)
+{
+ g_return_if_fail (CC_IS_WWAN_DATA (self));
+
+ self->home_only = !enable_roaming;
+
+ if (self->default_apn)
+ self->default_apn->modified = TRUE;
+}
+
+static void
+cc_wwan_data_apn_finalize (GObject *object)
+{
+ CcWwanDataApn *apn = CC_WWAN_DATA_APN (object);
+
+ wwan_data_apn_reset (apn);
+ g_clear_pointer (&apn->access_method,
+ nma_mobile_access_method_unref);
+
+ G_OBJECT_CLASS (cc_wwan_data_parent_class)->finalize (object);
+}
+
+static void
+cc_wwan_data_apn_class_init (CcWwanDataApnClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = cc_wwan_data_apn_finalize;
+}
+
+static void
+cc_wwan_data_apn_init (CcWwanDataApn *apn)
+{
+}
+
+CcWwanDataApn *
+cc_wwan_data_apn_new (void)
+{
+ return g_object_new (CC_TYPE_WWAN_DATA_APN, NULL);
+}
+
+/**
+ * cc_wwan_data_apn_get_name:
+ * @apn: A #CcWwanDataApn
+ *
+ * Get the Name of @apn
+ *
+ * Returns: (transfer none): The Name of @apn
+ */
+const gchar *
+cc_wwan_data_apn_get_name (CcWwanDataApn *apn)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), "");
+
+ if (apn->remote_connection)
+ return nm_connection_get_id (NM_CONNECTION (apn->remote_connection));
+
+ if (apn->access_method)
+ return nma_mobile_access_method_get_name (apn->access_method);
+
+ return "";
+}
+
+/**
+ * cc_wwan_data_apn_set_name:
+ * @apn: A #CcWwanDataApn
+ * @name: The name to be given for APN, should not
+ * be empty
+ *
+ * Set the name of @apn to be @name.
+ *
+ * @apn is only modified, use @cc_wwan_data_save_apn()
+ * to save the changes.
+ */
+void
+cc_wwan_data_apn_set_name (CcWwanDataApn *apn,
+ const gchar *name)
+{
+ NMConnection *connection;
+ NMSettingConnection *setting;
+
+ g_return_if_fail (CC_IS_WWAN_DATA_APN (apn));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (*name != '\0');
+
+ if (g_str_equal (cc_wwan_data_apn_get_name (apn), name))
+ return;
+
+ apn->modified = TRUE;
+ connection = wwan_data_get_nm_connection (apn);
+ setting = nm_connection_get_setting_connection (connection);
+ g_object_set (G_OBJECT (setting),
+ NM_SETTING_CONNECTION_ID, name,
+ NULL);
+}
+
+/**
+ * cc_wwan_data_apn_get_apn:
+ * @apn: A #CcWwanDataApn
+ *
+ * Get the APN of @apn
+ *
+ * Returns: (transfer none): The APN of @apn
+ */
+const gchar *
+cc_wwan_data_apn_get_apn (CcWwanDataApn *apn)
+{
+ const gchar *apn_name = "";
+
+ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), "");
+
+ if (apn->remote_connection)
+ {
+ NMSettingGsm *setting;
+
+ setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection));
+ apn_name = nm_setting_gsm_get_apn (setting);
+ }
+ else if (apn->access_method)
+ {
+ apn_name = nma_mobile_access_method_get_3gpp_apn (apn->access_method);
+ }
+
+ return apn_name ? apn_name : "";
+}
+
+/**
+ * cc_wwan_data_apn_set_apn:
+ * @apn: A #CcWwanDataApn
+ * @apn_name: The apn to be used, should not be
+ * empty
+ *
+ * Set the APN of @apn to @apn_name. @apn_name is
+ * usually a URL like “example.com” or a simple string
+ * like “internet”
+ *
+ * @apn is only modified, use @cc_wwan_data_save_apn()
+ * to save the changes.
+ */
+void
+cc_wwan_data_apn_set_apn (CcWwanDataApn *apn,
+ const gchar *apn_name)
+{
+ NMConnection *connection;
+ NMSettingGsm *setting;
+
+ g_return_if_fail (CC_IS_WWAN_DATA_APN (apn));
+ g_return_if_fail (apn_name != NULL);
+ g_return_if_fail (*apn_name != '\0');
+
+ if (g_str_equal (cc_wwan_data_apn_get_apn (apn), apn_name))
+ return;
+
+ apn->modified = TRUE;
+ connection = wwan_data_get_nm_connection (apn);
+ setting = nm_connection_get_setting_gsm (connection);
+ g_object_set (G_OBJECT (setting),
+ NM_SETTING_GSM_APN, apn_name,
+ NULL);
+}
+
+/**
+ * cc_wwan_data_apn_get_username:
+ * @apn: A #CcWwanDataApn
+ *
+ * Get the Username of @apn
+ *
+ * Returns: (transfer none): The Username of @apn
+ */
+const gchar *
+cc_wwan_data_apn_get_username (CcWwanDataApn *apn)
+{
+ const gchar *username = "";
+
+ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), "");
+
+ if (apn->remote_connection)
+ {
+ NMSettingGsm *setting;
+
+ setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection));
+ username = nm_setting_gsm_get_username (setting);
+ }
+ else if (apn->access_method)
+ {
+ username = nma_mobile_access_method_get_username (apn->access_method);
+ }
+
+ return username ? username : "";
+}
+
+/**
+ * cc_wwan_data_apn_set_username:
+ * @apn: A #CcWwanDataAPN
+ * @username: The username to be used
+ *
+ * Set the Username of @apn to @username.
+ *
+ * @apn is only modified, use @cc_wwan_data_save_apn()
+ * to save the changes.
+ */
+void
+cc_wwan_data_apn_set_username (CcWwanDataApn *apn,
+ const gchar *username)
+{
+ NMConnection *connection;
+ NMSettingGsm *setting;
+
+ g_return_if_fail (CC_IS_WWAN_DATA_APN (apn));
+
+ if (username && !*username)
+ username = NULL;
+
+ if (g_strcmp0 (cc_wwan_data_apn_get_username (apn), username) == 0)
+ return;
+
+ apn->modified = TRUE;
+ connection = wwan_data_get_nm_connection (apn);
+ setting = nm_connection_get_setting_gsm (connection);
+ g_object_set (G_OBJECT (setting),
+ NM_SETTING_GSM_USERNAME, username,
+ NULL);
+}
+
+/**
+ * cc_wwan_data_apn_get_password:
+ * @apn: A #CcWwanDataApn
+ *
+ * Get the Password of @apn
+ *
+ * Returns: (transfer none): The Password of @apn
+ */
+const gchar *
+cc_wwan_data_apn_get_password (CcWwanDataApn *apn)
+{
+ const gchar *password = "";
+
+ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), "");
+
+ if (NM_IS_REMOTE_CONNECTION (apn->remote_connection))
+ {
+ g_autoptr(GVariant) secrets = NULL;
+ g_autoptr(GError) error = NULL;
+
+ secrets = nm_remote_connection_get_secrets (NM_REMOTE_CONNECTION (apn->remote_connection),
+ "gsm", NULL, &error);
+
+ if (!error)
+ nm_connection_update_secrets (NM_CONNECTION (apn->remote_connection),
+ "gsm", secrets, &error);
+
+ if (error)
+ {
+ g_warning ("Error: %s", error->message);
+ return "";
+ }
+ }
+
+ if (apn->remote_connection)
+ {
+ NMSettingGsm *setting;
+
+ setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection));
+ password = nm_setting_gsm_get_password (setting);
+ }
+ else if (apn->access_method)
+ {
+ password = nma_mobile_access_method_get_password (apn->access_method);
+ }
+
+ return password ? password : "";
+
+ if (apn->remote_connection)
+ nm_connection_clear_secrets (NM_CONNECTION (apn->remote_connection));
+}
+
+/**
+ * cc_wwan_data_apn_set_password:
+ * @apn: A #CcWwanDataApn
+ * @password: The password to be used
+ *
+ * Set the Password of @apn to @password.
+ *
+ * @apn is only modified, use @cc_wwan_data_save_apn()
+ * to save the changes.
+ */
+void
+cc_wwan_data_apn_set_password (CcWwanDataApn *apn,
+ const gchar *password)
+{
+ NMConnection *connection;
+ NMSettingGsm *setting;
+
+ g_return_if_fail (CC_IS_WWAN_DATA_APN (apn));
+
+ if (password && !*password)
+ password = NULL;
+
+ if (g_strcmp0 (cc_wwan_data_apn_get_password (apn), password) == 0)
+ return;
+
+ apn->modified = TRUE;
+ connection = wwan_data_get_nm_connection (apn);
+ setting = nm_connection_get_setting_gsm (connection);
+ g_object_set (G_OBJECT (setting),
+ NM_SETTING_GSM_PASSWORD, password,
+ NULL);
+}
+
+gint
+cc_wwan_data_get_priority (CcWwanData *self)
+{
+ CcWwanDataApn *apn;
+ NMSettingIPConfig *setting;
+
+ g_return_val_if_fail (CC_IS_WWAN_DATA (self),
+ CC_WWAN_APN_PRIORITY_LOW);
+
+ apn = self->default_apn;
+
+ if (!apn || !apn->remote_connection)
+ return CC_WWAN_APN_PRIORITY_LOW;
+
+ setting = nm_connection_get_setting_ip4_config (NM_CONNECTION (apn->remote_connection));
+
+ /* Lower the number, higher the priority */
+ if (nm_setting_ip_config_get_route_metric (setting) <= CC_WWAN_ROUTE_PRIORITY_HIGH)
+ return CC_WWAN_APN_PRIORITY_HIGH;
+ else
+ return CC_WWAN_APN_PRIORITY_LOW;
+}
+
+void
+cc_wwan_data_set_priority (CcWwanData *self,
+ int priority)
+{
+ g_return_if_fail (CC_IS_WWAN_DATA (self));
+ g_return_if_fail (priority == CC_WWAN_APN_PRIORITY_LOW ||
+ priority == CC_WWAN_APN_PRIORITY_HIGH);
+
+ if (self->priority == priority)
+ return;
+
+ self->priority = priority;
+
+ if (self->default_apn)
+ self->default_apn->modified = TRUE;
+}
diff --git a/panels/wwan/cc-wwan-data.h b/panels/wwan/cc-wwan-data.h
new file mode 100644
index 000000000..9572b862d
--- /dev/null
+++ b/panels/wwan/cc-wwan-data.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-data.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <libmm-glib.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+#define CC_WWAN_APN_PRIORITY_LOW (1)
+#define CC_WWAN_APN_PRIORITY_HIGH (2)
+
+#define CC_TYPE_WWAN_DATA_APN (cc_wwan_data_apn_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanDataApn, cc_wwan_data_apn, CC, WWAN_DATA_APN, GObject)
+
+#define CC_TYPE_WWAN_DATA (cc_wwan_data_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanData, cc_wwan_data, CC, WWAN_DATA, GObject)
+
+CcWwanData *cc_wwan_data_new (MMObject *mm_object,
+ NMClient *nm_client);
+GError *cc_wwan_data_get_error (CcWwanData *self);
+const gchar *cc_wwan_data_get_simple_html_error (CcWwanData *self);
+GListModel *cc_wwan_data_get_apn_list (CcWwanData *self);
+void cc_wwan_data_save_apn (CcWwanData *self,
+ CcWwanDataApn *apn,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CcWwanDataApn *cc_wwan_data_save_apn_finish (CcWwanData *self,
+ GAsyncResult *result,
+ GError **error);
+void cc_wwan_data_save_settings (CcWwanData *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean cc_wwan_data_save_settings_finish (CcWwanData *self,
+ GAsyncResult *result,
+ GError **error);
+gboolean cc_wwan_data_delete_apn (CcWwanData *self,
+ CcWwanDataApn *apn,
+ GCancellable *cancellable,
+ GError **error);
+gboolean cc_wwan_data_set_default_apn (CcWwanData *self,
+ CcWwanDataApn *apn);
+CcWwanDataApn *cc_wwan_data_get_default_apn (CcWwanData *self);
+gboolean cc_wwan_data_get_enabled (CcWwanData *self);
+void cc_wwan_data_set_enabled (CcWwanData *self,
+ gboolean enabled);
+gboolean cc_wwan_data_get_roaming_enabled (CcWwanData *self);
+void cc_wwan_data_set_roaming_enabled (CcWwanData *self,
+ gboolean enable_roaming);
+
+CcWwanDataApn *cc_wwan_data_apn_new (void);
+const gchar *cc_wwan_data_apn_get_name (CcWwanDataApn *apn);
+void cc_wwan_data_apn_set_name (CcWwanDataApn *apn,
+ const gchar *name);
+const gchar *cc_wwan_data_apn_get_apn (CcWwanDataApn *apn);
+void cc_wwan_data_apn_set_apn (CcWwanDataApn *apn,
+ const gchar *apn_name);
+const gchar *cc_wwan_data_apn_get_username (CcWwanDataApn *apn);
+void cc_wwan_data_apn_set_username (CcWwanDataApn *apn,
+ const gchar *username);
+const gchar *cc_wwan_data_apn_get_password (CcWwanDataApn *apn);
+void cc_wwan_data_apn_set_password (CcWwanDataApn *apn,
+ const gchar *password);
+gint cc_wwan_data_get_priority (CcWwanData *self);
+void cc_wwan_data_set_priority (CcWwanData *self,
+ int priority);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-details-dialog.c b/panels/wwan/cc-wwan-details-dialog.c
new file mode 100644
index 000000000..59e8dc361
--- /dev/null
+++ b/panels/wwan/cc-wwan-details-dialog.c
@@ -0,0 +1,257 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-network-dialog.c
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wwan-details-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "list-box-helper.h"
+#include "cc-wwan-details-dialog.h"
+#include "cc-wwan-resources.h"
+
+/**
+ * @short_description: Dialog to Show Device Details
+ */
+
+struct _CcWwanDetailsDialog
+{
+ GtkDialog parent_instance;
+
+ GtkLabel *device_identifier;
+ GtkLabel *device_model;
+ GtkLabel *firmware_version;
+ GtkLabel *identifier_label;
+ GtkLabel *manufacturer;
+ GtkLabel *network_status;
+ GtkLabel *network_type;
+ GtkLabel *operator_name;
+ GtkLabel *own_numbers;
+ GtkLabel *signal_strength;
+
+ CcWwanDevice *device;
+};
+
+G_DEFINE_TYPE (CcWwanDetailsDialog, cc_wwan_details_dialog, GTK_TYPE_DIALOG)
+
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+cc_wwan_details_update_network_status (CcWwanDetailsDialog *self)
+{
+ CcWwanState state;
+
+ g_assert (CC_IS_WWAN_DETAILS_DIALOG (self));
+
+ state = cc_wwan_device_get_network_state (self->device);
+
+ switch (state)
+ {
+ case CC_WWAN_REGISTRATION_STATE_IDLE:
+ gtk_label_set_label (self->network_status, _("Not Registered"));
+ break;
+
+ case CC_WWAN_REGISTRATION_STATE_REGISTERED:
+ gtk_label_set_label (self->network_status, _("Registered"));
+ break;
+
+ case CC_WWAN_REGISTRATION_STATE_ROAMING:
+ gtk_label_set_label (self->network_status, _("Roaming"));
+ break;
+
+ case CC_WWAN_REGISTRATION_STATE_SEARCHING:
+ gtk_label_set_label (self->network_status, _("Searching"));
+ break;
+
+ case CC_WWAN_REGISTRATION_STATE_DENIED:
+ gtk_label_set_label (self->network_status, _("Denied"));
+ break;
+
+ default:
+ gtk_label_set_label (self->network_status, _("Unknown"));
+ break;
+ }
+}
+
+static void
+cc_wwan_details_signal_changed_cb (CcWwanDetailsDialog *self)
+{
+ g_autofree gchar *network_type_string = NULL;
+ g_autofree gchar *signal_string = NULL;
+ const gchar *operator_name;
+
+ g_assert (CC_IS_WWAN_DETAILS_DIALOG (self));
+
+ operator_name = cc_wwan_device_get_operator_name (self->device);
+ if (operator_name)
+ gtk_label_set_label (self->operator_name, operator_name);
+
+ network_type_string = cc_wwan_device_dup_network_type_string (self->device);
+ if (network_type_string)
+ gtk_label_set_label (self->network_type, network_type_string);
+
+ signal_string = cc_wwan_device_dup_signal_string (self->device);
+ if (signal_string)
+ gtk_label_set_label (self->signal_strength, signal_string);
+
+ cc_wwan_details_update_network_status (self);
+}
+
+static void
+cc_wwan_details_update_hardware_details (CcWwanDetailsDialog *self)
+{
+ const gchar *str;
+
+ g_assert (CC_IS_WWAN_DETAILS_DIALOG (self));
+
+ str = cc_wwan_device_get_manufacturer (self->device);
+ if (str)
+ gtk_label_set_label (self->manufacturer, str);
+
+ str = cc_wwan_device_get_model (self->device);
+ if (str)
+ gtk_label_set_label (self->device_model, str);
+
+ str = cc_wwan_device_get_firmware_version (self->device);
+ if (str)
+ gtk_label_set_label (self->firmware_version, str);
+
+ str = cc_wwan_device_get_identifier (self->device);
+ if (str)
+ gtk_label_set_label (self->device_identifier, str);
+}
+
+static void
+cc_wwan_details_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWwanDetailsDialog *self = CC_WWAN_DETAILS_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_DEVICE:
+ self->device = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wwan_details_dialog_constructed (GObject *object)
+{
+ CcWwanDetailsDialog *self = CC_WWAN_DETAILS_DIALOG (object);
+ g_autofree char *numbers = NULL;
+
+ G_OBJECT_CLASS (cc_wwan_details_dialog_parent_class)->constructed (object);
+
+ g_signal_connect_object (self->device, "notify::signal",
+ G_CALLBACK (cc_wwan_details_signal_changed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ numbers = cc_wwan_device_dup_own_numbers (self->device);
+ gtk_widget_set_visible (GTK_WIDGET (self->own_numbers), !!numbers);
+
+ if (numbers)
+ gtk_label_set_text (self->own_numbers, numbers);
+
+ cc_wwan_details_signal_changed_cb (self);
+ cc_wwan_details_update_hardware_details (self);
+}
+
+static void
+cc_wwan_details_dialog_dispose (GObject *object)
+{
+ CcWwanDetailsDialog *self = CC_WWAN_DETAILS_DIALOG (object);
+
+ g_clear_object (&self->device);
+
+ G_OBJECT_CLASS (cc_wwan_details_dialog_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_details_dialog_class_init (CcWwanDetailsDialogClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = cc_wwan_details_dialog_set_property;
+ object_class->constructed = cc_wwan_details_dialog_constructed;
+ object_class->dispose = cc_wwan_details_dialog_dispose;
+
+ properties[PROP_DEVICE] =
+ g_param_spec_object ("device",
+ "Device",
+ "The WWAN Device",
+ CC_TYPE_WWAN_DEVICE,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/wwan/cc-wwan-details-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, device_identifier);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, device_model);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, firmware_version);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, identifier_label);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, manufacturer);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, network_status);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, network_type);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, operator_name);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, own_numbers);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, signal_strength);
+}
+
+static void
+cc_wwan_details_dialog_init (CcWwanDetailsDialog *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcWwanDetailsDialog *
+cc_wwan_details_dialog_new (GtkWindow *parent_window,
+ CcWwanDevice *device)
+{
+ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+ return g_object_new (CC_TYPE_WWAN_DETAILS_DIALOG,
+ "transient-for", parent_window,
+ "use-header-bar", 1,
+ "device", device,
+ NULL);
+}
diff --git a/panels/wwan/cc-wwan-details-dialog.h b/panels/wwan/cc-wwan-details-dialog.h
new file mode 100644
index 000000000..7e7812cde
--- /dev/null
+++ b/panels/wwan/cc-wwan-details-dialog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-details-dialog.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <handy.h>
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_DETAILS_DIALOG (cc_wwan_details_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanDetailsDialog, cc_wwan_details_dialog, CC, WWAN_DETAILS_DIALOG, GtkDialog)
+
+CcWwanDetailsDialog *cc_wwan_details_dialog_new (GtkWindow *parent_window,
+ CcWwanDevice *device);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-details-dialog.ui b/panels/wwan/cc-wwan-details-dialog.ui
new file mode 100644
index 000000000..042d3ee33
--- /dev/null
+++ b/panels/wwan/cc-wwan-details-dialog.ui
@@ -0,0 +1,320 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcWwanDetailsDialog" parent="GtkDialog">
+ <property name="title" translatable="yes">Modem Details</property>
+ <property name="default-height">480</property>
+ <property name="default-width">360</property>
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="border-width">0</property>
+ <property name="width-request">340</property>
+ <property name="height-request">360</property>
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">1</property>
+ <property name="margin-top">32</property>
+ <property name="margin-bottom">32</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+
+ <!-- Modem Status Title -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="margin-bottom">12</property>
+ <property name="label" translatable="yes">Modem Status</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+
+ <!-- Modem Status Content -->
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">1</property>
+ <property name="row-spacing">9</property>
+ <property name="column-spacing">6</property>
+ <property name="margin-bottom">24</property>
+
+ <!-- Carrier -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Carrier</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="operator_name">
+ <property name="visible">1</property>
+ <property name="xalign">0.0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <!-- Network Type -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Network Type</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="network_type">
+ <property name="visible">1</property>
+ <property name="xalign">0.0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+
+ <!-- Signal Strength -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Signal Strength</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="signal_strength">
+ <property name="visible">1</property>
+ <property name="xalign">0.0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+
+ <!-- Network Status -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Network Status</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="network_status">
+ <property name="visible">1</property>
+ <property name="xalign">0.0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+
+ <!-- Own Numbers -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible" bind-source="own_numbers"
+ bind-property="visible" bind-flags="sync-create"/>
+ <property name="label" translatable="yes">Own Number</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="own_numbers">
+ <property name="visible">1</property>
+ <property name="xalign">0.0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+
+ <!-- Device Details Title -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="margin-bottom">12</property>
+ <property name="label" translatable="yes">Device Details</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">5</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+
+ <!-- Device Details Content -->
+ <!-- Manufacturer -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Manufacturer</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="manufacturer">
+ <property name="visible">1</property>
+ <property name="xalign">0.0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">6</property>
+ </packing>
+ </child>
+
+ <!-- Model -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Model</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="device_model">
+ <property name="visible">1</property>
+ <property name="xalign">0.0</property>
+ <property name="selectable">1</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">7</property>
+ </packing>
+ </child>
+
+ <!-- Firmware version -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Firmware Version</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="firmware_version">
+ <property name="visible">1</property>
+ <property name="selectable">1</property>
+ <property name="xalign">0.0</property>
+ <property name="ellipsize">end</property>
+ <property name="wrap">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">8</property>
+ </packing>
+ </child>
+
+ <!-- IMEI/ICCID -->
+ <child>
+ <object class="GtkLabel" id="identifier_label">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">IMEI</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">9</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="device_identifier">
+ <property name="visible">1</property>
+ <property name="selectable">1</property>
+ <property name="xalign">0.0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">9</property>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </object> <!-- ./HdyClamp -->
+ </child>
+ </object>
+ </child> <!-- ./internal-child -->
+
+ </template>
+</interface>
diff --git a/panels/wwan/cc-wwan-device-page.c b/panels/wwan/cc-wwan-device-page.c
new file mode 100644
index 000000000..0a04d3379
--- /dev/null
+++ b/panels/wwan/cc-wwan-device-page.c
@@ -0,0 +1,634 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-device-page.c
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wwan-device-page"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+#define GCR_API_SUBJECT_TO_CHANGE
+#include <gcr/gcr.h>
+
+#include "list-box-helper.h"
+#include "cc-list-row.h"
+#include "cc-wwan-data.h"
+#include "cc-wwan-mode-dialog.h"
+#include "cc-wwan-network-dialog.h"
+#include "cc-wwan-details-dialog.h"
+#include "cc-wwan-sim-lock-dialog.h"
+#include "cc-wwan-apn-dialog.h"
+#include "cc-wwan-device-page.h"
+#include "cc-wwan-resources.h"
+
+#include "shell/cc-application.h"
+#include "shell/cc-debug.h"
+#include "shell/cc-object-storage.h"
+
+/**
+ * @short_description: Device settings page
+ * @include: "cc-wwan-device-page.h"
+ *
+ * The Device page allows users to configure device
+ * settings. Please note that there is no one-to-one
+ * maping for a device settings page and a physical
+ * device. Say, if a device have two SIM card slots,
+ * there should be two device pages, one for each SIM.
+ */
+
+struct _CcWwanDevicePage
+{
+ GtkBox parent_instance;
+
+ GtkListBox *advanced_settings_list;
+ CcListRow *apn_settings_row;
+ CcListRow *data_enable_row;
+ CcListRow *data_roaming_row;
+ GtkListBox *data_settings_list;
+ CcListRow *details_row;
+ GtkStack *main_stack;
+ CcListRow *network_mode_row;
+ CcListRow *network_name_row;
+ GtkListBox *network_settings_list;
+ CcListRow *sim_lock_row;
+ GtkButton *unlock_button;
+
+ GtkLabel *notification_label;
+
+ CcWwanDevice *device;
+ CcWwanData *wwan_data;
+ GDBusProxy *wwan_proxy;
+
+ CcWwanApnDialog *apn_dialog;
+ CcWwanDetailsDialog *details_dialog;
+ CcWwanModeDialog *network_mode_dialog;
+ CcWwanNetworkDialog *network_dialog;
+ CcWwanSimLockDialog *sim_lock_dialog;
+
+ gint sim_index;
+ /* Set if a change is triggered in a signal’s callback,
+ * to avoid re-triggering of callback. This is used
+ * instead of blocking handlers where the signal may be
+ * emitted async and the block/unblock may not work right
+ */
+ gboolean is_self_change;
+ gboolean is_retry;
+};
+
+G_DEFINE_TYPE (CcWwanDevicePage, cc_wwan_device_page, GTK_TYPE_BOX)
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+wwan_data_show_apn_dialog (CcWwanDevicePage *self)
+{
+ GtkWindow *top_level;
+
+ top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)));
+
+ if (!self->apn_dialog)
+ self->apn_dialog = cc_wwan_apn_dialog_new (top_level, self->device);
+
+ gtk_widget_show (GTK_WIDGET (self->apn_dialog));
+}
+
+static GcrPrompt *
+cc_wwan_device_page_new_prompt (CcWwanDevicePage *self,
+ MMModemLock lock)
+{
+ GcrPrompt *prompt;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *description = NULL;
+ g_autofree gchar *warning = NULL;
+ const gchar *message = NULL;
+ guint num;
+
+ prompt = GCR_PROMPT (gcr_system_prompt_open (-1, NULL, &error));
+
+ if (error)
+ {
+ g_warning ("Error opening Prompt: %s", error->message);
+ return NULL;
+ }
+
+ gcr_prompt_set_title (prompt, _("Unlock SIM card"));
+ gcr_prompt_set_continue_label (prompt, _("Unlock"));
+ gcr_prompt_set_cancel_label (prompt, _("Cancel"));
+
+ if (lock == MM_MODEM_LOCK_SIM_PIN)
+ {
+ description = g_strdup_printf (_("Please provide PIN code for SIM %d"), self->sim_index);
+ message = _("Enter PIN to unlock your SIM card");
+ }
+ else if (lock == MM_MODEM_LOCK_SIM_PUK)
+ {
+ description = g_strdup_printf (_("Please provide PUK code for SIM %d"), self->sim_index);
+ message = _("Enter PUK to unlock your SIM card");
+ }
+ else
+ {
+ g_warn_if_reached ();
+ g_object_unref (prompt);
+
+ return NULL;
+ }
+
+ gcr_prompt_set_description (prompt, description);
+ gcr_prompt_set_message (prompt, message);
+
+ num = cc_wwan_device_get_unlock_retries (self->device, lock);
+
+ if (num != MM_UNLOCK_RETRIES_UNKNOWN)
+ {
+ if (self->is_retry)
+ warning = g_strdup_printf (ngettext ("Wrong password entered. You have %1$u try left",
+ "Wrong password entered. You have %1$u tries left", num), num);
+ else
+ warning = g_strdup_printf (ngettext ("You have %u try left",
+ "You have %u tries left", num), num);
+ }
+ else if (self->is_retry)
+ {
+ warning = g_strdup (_("Wrong password entered."));
+ }
+
+ gcr_prompt_set_warning (prompt, warning);
+
+ return prompt;
+}
+
+static void
+wwan_update_unlock_button (CcWwanDevicePage *self)
+{
+ gtk_button_set_label (self->unlock_button, _("Unlock"));
+ gtk_widget_set_sensitive (GTK_WIDGET (self->unlock_button), TRUE);
+}
+
+static void
+cc_wwan_device_page_unlocked_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanDevicePage *self = user_data;
+ wwan_update_unlock_button (self);
+}
+
+static void
+wwan_device_unlock_clicked_cb (CcWwanDevicePage *self)
+{
+ g_autoptr(GError) error = NULL;
+ GcrPrompt *prompt;
+ const gchar *password, *warning;
+ const gchar *pin = "";
+ const gchar *puk = "";
+ MMModemLock lock;
+
+ lock = cc_wwan_device_get_lock (self->device);
+ password = "";
+
+ if (lock != MM_MODEM_LOCK_SIM_PIN &&
+ lock != MM_MODEM_LOCK_SIM_PUK)
+ g_return_if_reached ();
+
+ if (lock == MM_MODEM_LOCK_SIM_PUK)
+ {
+ prompt = cc_wwan_device_page_new_prompt (self, lock);
+
+ warning = _("PUK code should be an 8 digit number");
+ while (password && !cc_wwan_device_pin_valid (password, lock))
+ {
+ password = gcr_prompt_password (prompt, NULL, &error);
+ gcr_prompt_set_warning (prompt, warning);
+ }
+
+ puk = g_strdup (password);
+ password = "";
+ gcr_prompt_close (prompt);
+ g_object_unref (prompt);
+
+ if (error)
+ g_warning ("Error: %s", error->message);
+
+ /* Error or User cancelled PUK */
+ if (!puk)
+ return;
+ }
+
+ prompt = cc_wwan_device_page_new_prompt (self, MM_MODEM_LOCK_SIM_PIN);
+ if (lock == MM_MODEM_LOCK_SIM_PUK)
+ {
+ gcr_prompt_set_password_new (prompt, TRUE);
+ gcr_prompt_set_message (prompt, _("Enter New PIN"));
+ gcr_prompt_set_warning (prompt, "");
+ }
+
+ warning = _("PIN code should be a 4-8 digit number");
+ while (password && !cc_wwan_device_pin_valid (password, MM_MODEM_LOCK_SIM_PIN))
+ {
+ password = gcr_prompt_password (prompt, NULL, &error);
+ gcr_prompt_set_warning (prompt, warning);
+ }
+
+ pin = g_strdup (password);
+ gcr_prompt_close (prompt);
+ g_object_unref (prompt);
+
+ if (error)
+ g_warning ("Error: %s", error->message);
+
+ /* Error or User cancelled PIN */
+ if (!pin)
+ return;
+
+ gtk_button_set_label (self->unlock_button, _("Unlocking..."));
+ gtk_widget_set_sensitive (GTK_WIDGET (self->unlock_button), FALSE);
+
+ if (lock == MM_MODEM_LOCK_SIM_PIN)
+ cc_wwan_device_send_pin (self->device, pin,
+ NULL, /* cancellable */
+ cc_wwan_device_page_unlocked_cb,
+ self);
+ else if (lock == MM_MODEM_LOCK_SIM_PUK)
+ {
+ cc_wwan_device_send_puk (self->device, puk, pin,
+ NULL, /* Cancellable */
+ cc_wwan_device_page_unlocked_cb,
+ self);
+ }
+ else
+ {
+ g_warn_if_reached ();
+ }
+}
+
+static void
+wwan_data_settings_changed_cb (CcWwanDevicePage *self,
+ GParamSpec *pspec,
+ CcListRow *data_row)
+{
+ gboolean active;
+
+ if (self->is_self_change)
+ {
+ self->is_self_change = FALSE;
+ return;
+ }
+
+ if (cc_wwan_data_get_default_apn (self->wwan_data) == NULL)
+ wwan_data_show_apn_dialog (self);
+
+ /* The user dismissed the dialog for selecting default APN */
+ if (cc_wwan_data_get_default_apn (self->wwan_data) == NULL)
+ {
+ self->is_self_change = TRUE;
+ gtk_widget_activate (GTK_WIDGET (data_row));
+
+ return;
+ }
+
+ active = cc_list_row_get_active (data_row);
+
+ if (data_row == self->data_enable_row)
+ cc_wwan_data_set_enabled (self->wwan_data, active);
+ else
+ cc_wwan_data_set_roaming_enabled (self->wwan_data, active);
+
+ cc_wwan_data_save_settings (self->wwan_data, NULL, NULL, NULL);
+}
+
+static void
+wwan_network_settings_activated_cb (CcWwanDevicePage *self,
+ CcListRow *row)
+{
+ GtkWidget *dialog;
+ GtkWindow *top_level;
+
+ top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)));
+
+ if (row == self->network_mode_row)
+ {
+ if (!self->network_mode_dialog)
+ self->network_mode_dialog = cc_wwan_mode_dialog_new (top_level, self->device);
+
+ dialog = GTK_WIDGET (self->network_mode_dialog);
+ }
+ else if (row == self->network_name_row)
+ {
+ if (!self->network_dialog)
+ self->network_dialog = cc_wwan_network_dialog_new (top_level, self->device);
+
+ dialog = GTK_WIDGET (self->network_dialog);
+ }
+ else
+ {
+ return;
+ }
+
+ gtk_widget_show (dialog);
+}
+
+static void
+wwan_advanced_settings_activated_cb (CcWwanDevicePage *self,
+ CcListRow *row)
+{
+ GtkWindow *top_level;
+
+ top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)));
+
+ if (row == self->sim_lock_row)
+ {
+ if (!self->sim_lock_dialog)
+ self->sim_lock_dialog = cc_wwan_sim_lock_dialog_new (top_level, self->device);
+ gtk_widget_show (GTK_WIDGET (self->sim_lock_dialog));
+ }
+ else if (row == self->details_row)
+ {
+ if (!self->details_dialog)
+ self->details_dialog = cc_wwan_details_dialog_new (top_level, self->device);
+ gtk_widget_show (GTK_WIDGET (self->details_dialog));
+ }
+ else if (row == self->apn_settings_row)
+ {
+ wwan_data_show_apn_dialog (self);
+ }
+ else
+ {
+ g_return_if_reached ();
+ }
+}
+
+static void
+cc_wwan_device_page_update_data (CcWwanDevicePage *self)
+{
+ gboolean has_data;
+
+ if (self->wwan_data == cc_wwan_device_get_data (self->device))
+ return;
+
+ self->wwan_data = cc_wwan_device_get_data (self->device);
+ has_data = self->wwan_data != NULL;
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->data_settings_list), has_data);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->apn_settings_row), has_data);
+
+ if (!has_data)
+ return;
+
+ g_signal_handlers_block_by_func (self->data_roaming_row,
+ wwan_data_settings_changed_cb, self);
+ g_signal_handlers_block_by_func (self->data_enable_row,
+ wwan_data_settings_changed_cb, self);
+
+ g_object_set (self->data_roaming_row, "active",
+ cc_wwan_data_get_roaming_enabled (self->wwan_data), NULL);
+
+ g_object_set (self->data_enable_row, "active",
+ cc_wwan_data_get_enabled (self->wwan_data), NULL);
+
+ g_signal_handlers_unblock_by_func (self->data_roaming_row,
+ wwan_data_settings_changed_cb, self);
+ g_signal_handlers_unblock_by_func (self->data_enable_row,
+ wwan_data_settings_changed_cb, self);
+}
+
+static void
+cc_wwan_device_page_update (CcWwanDevicePage *self)
+{
+ GtkStack *main_stack;
+ MMModemLock lock;
+
+ main_stack = self->main_stack;
+ if (!cc_wwan_device_has_sim (self->device))
+ gtk_stack_set_visible_child_name (main_stack, "no-sim-view");
+ else if ((lock = cc_wwan_device_get_lock (self->device)) == MM_MODEM_LOCK_SIM_PIN ||
+ lock == MM_MODEM_LOCK_SIM_PUK)
+ gtk_stack_set_visible_child_name (main_stack, "sim-lock-view");
+ else
+ gtk_stack_set_visible_child_name (main_stack, "settings-view");
+}
+
+static void
+cc_wwan_locks_changed_cb (CcWwanDevicePage *self)
+{
+ const gchar *label;
+
+ if (cc_wwan_device_get_sim_lock (self->device))
+ label = _("Enabled");
+ else
+ label = _("Disabled");
+
+ cc_list_row_set_secondary_label (self->sim_lock_row, label);
+ cc_wwan_device_page_update (self);
+}
+
+static void
+cc_wwan_device_page_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWwanDevicePage *self = (CcWwanDevicePage *)object;
+
+ switch (prop_id)
+ {
+ case PROP_DEVICE:
+ self->device = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wwan_device_page_constructed (GObject *object)
+{
+ CcWwanDevicePage *self = (CcWwanDevicePage *)object;
+
+ G_OBJECT_CLASS (cc_wwan_device_page_parent_class)->constructed (object);
+
+ cc_wwan_device_page_update_data (self);
+
+ g_object_bind_property (self->device, "operator-name",
+ self->network_name_row, "secondary-label",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (self->device, "network-mode",
+ self->network_mode_row, "secondary-label",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ g_signal_connect_object (self->device, "notify::enabled-locks",
+ (GCallback)cc_wwan_locks_changed_cb,
+ self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->device, "notify::has-data",
+ (GCallback)cc_wwan_device_page_update_data,
+ self, G_CONNECT_SWAPPED);
+
+ cc_wwan_device_page_update (self);
+ cc_wwan_locks_changed_cb (self);
+}
+
+static void
+cc_wwan_device_page_dispose (GObject *object)
+{
+ CcWwanDevicePage *self = (CcWwanDevicePage *)object;
+
+ g_clear_pointer ((GtkWidget **)&self->apn_dialog, gtk_widget_destroy);
+ g_clear_pointer ((GtkWidget **)&self->details_dialog, gtk_widget_destroy);
+ g_clear_pointer ((GtkWidget **)&self->network_mode_dialog, gtk_widget_destroy);
+ g_clear_pointer ((GtkWidget **)&self->network_dialog, gtk_widget_destroy);
+ g_clear_pointer ((GtkWidget **)&self->sim_lock_dialog, gtk_widget_destroy);
+
+ g_clear_object (&self->wwan_proxy);
+ g_clear_object (&self->device);
+
+ G_OBJECT_CLASS (cc_wwan_device_page_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_device_page_class_init (CcWwanDevicePageClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = cc_wwan_device_page_set_property;
+ object_class->constructed = cc_wwan_device_page_constructed;
+ object_class->dispose = cc_wwan_device_page_dispose;
+
+ g_type_ensure (CC_TYPE_WWAN_DEVICE);
+
+ properties[PROP_DEVICE] =
+ g_param_spec_object ("device",
+ "Device",
+ "The WWAN Device",
+ CC_TYPE_WWAN_DEVICE,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/wwan/cc-wwan-device-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, advanced_settings_list);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, apn_settings_row);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, data_enable_row);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, data_roaming_row);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, data_settings_list);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, details_row);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, main_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, network_mode_row);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, network_name_row);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, network_settings_list);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, sim_lock_row);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, unlock_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, wwan_device_unlock_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, wwan_data_settings_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, wwan_network_settings_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, wwan_advanced_settings_activated_cb);
+}
+
+static void
+cc_wwan_device_page_init (CcWwanDevicePage *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_list_box_set_header_func (self->data_settings_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (self->network_settings_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (self->advanced_settings_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+}
+
+static void
+cc_wwan_error_changed_cb (CcWwanDevicePage *self)
+{
+ const gchar *message;
+
+ message = cc_wwan_device_get_simple_error (self->device);
+
+ if (!message)
+ return;
+
+ /*
+ * The label is first set to empty, which will result in
+ * the revealer to be closed. Then the real label is
+ * set. This will animate the revealer which can bring
+ * the user's attention.
+ */
+ gtk_label_set_label (self->notification_label, "");
+ gtk_label_set_label (self->notification_label, message);
+}
+
+CcWwanDevicePage *
+cc_wwan_device_page_new (CcWwanDevice *device,
+ GtkWidget *notification_label)
+{
+ CcWwanDevicePage *self;
+
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+ self = g_object_new (CC_TYPE_WWAN_DEVICE_PAGE,
+ "device", device,
+ NULL);
+
+ self->notification_label = GTK_LABEL (notification_label);
+
+ g_signal_connect_object (self->device, "notify::error",
+ G_CALLBACK (cc_wwan_error_changed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ return self;
+}
+
+CcWwanDevice *
+cc_wwan_device_page_get_device (CcWwanDevicePage *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE_PAGE (self), NULL);
+
+ return self->device;
+}
+
+void
+cc_wwan_device_page_set_sim_index (CcWwanDevicePage *self,
+ gint sim_index)
+{
+ g_return_if_fail (CC_IS_WWAN_DEVICE_PAGE (self));
+ g_return_if_fail (sim_index >= 1);
+
+ self->sim_index = sim_index;
+}
diff --git a/panels/wwan/cc-wwan-device-page.h b/panels/wwan/cc-wwan-device-page.h
new file mode 100644
index 000000000..923346a89
--- /dev/null
+++ b/panels/wwan/cc-wwan-device-page.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-device-page.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_DEVICE_PAGE (cc_wwan_device_page_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanDevicePage, cc_wwan_device_page, CC, WWAN_DEVICE_PAGE, GtkBox)
+
+CcWwanDevicePage *cc_wwan_device_page_new (CcWwanDevice *device,
+ GtkWidget *notification_label);
+CcWwanDevice *cc_wwan_device_page_get_device (CcWwanDevicePage *self);
+void cc_wwan_device_page_set_sim_index (CcWwanDevicePage *self,
+ gint sim_index);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-device-page.ui b/panels/wwan/cc-wwan-device-page.ui
new file mode 100644
index 000000000..f77bd707d
--- /dev/null
+++ b/panels/wwan/cc-wwan-device-page.ui
@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcWwanDevicePage" parent="GtkBox">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkStack" id="main_stack">
+ <property name="visible">1</property>
+ <property name="homogeneous">0</property>
+
+ <!-- SIM not inserted view -->
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="expand">1</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="margin-bottom">64</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">auth-sim-missing</property>
+ <property name="pixel-size">192</property>
+ <property name="margin-bottom">18</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="label" translatable="yes">No SIM</property>
+ <attributes>
+ <attribute name="weight" value="bold" />
+ <attribute name="scale" value="1.2" />
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="label" translatable="yes">Insert a SIM card to use this modem</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">no-sim-view</property>
+ </packing>
+ </child>
+
+ <!-- SIM locked view -->
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="expand">1</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="margin-bottom">64</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">auth-sim-locked</property>
+ <property name="pixel-size">192</property>
+ <property name="margin-bottom">18</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="label" translatable="yes">SIM Locked</property>
+ <property name="margin-bottom">32</property>
+ <attributes>
+ <attribute name="weight" value="bold" />
+ <attribute name="scale" value="1.2" />
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="unlock_button">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Unlock</property>
+ <signal name="clicked" handler="wwan_device_unlock_clicked_cb" swapped="yes" />
+ <style>
+ <class name="suggested-action" />
+ </style>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">sim-lock-view</property>
+ </packing>
+ </child> <!-- -->
+
+ <!-- Network Settings -->
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="margin-top">18</property>
+ <property name="orientation">vertical</property>
+
+ <!-- Network Settings Title -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Network</property>
+ <property name="xalign">0.0</property>
+ <property name="margin-bottom">12</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+
+ <!-- Internet settings -->
+ <child>
+ <object class="GtkListBox" id="data_settings_list">
+ <property name="visible">1</property>
+ <property name="margin-bottom">32</property>
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="frame" />
+ </style>
+
+ <!-- Enable/Disable Data -->
+ <child>
+ <object class="CcListRow" id="data_enable_row">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="show-switch">1</property>
+ <property name="title" translatable="yes">_Mobile Data</property>
+ <property name="subtitle" translatable="yes">Access data using mobile network</property>
+ <signal name="notify::active" handler="wwan_data_settings_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- Data Roaming -->
+ <child>
+ <object class="CcListRow" id="data_roaming_row">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="show-switch">1</property>
+ <property name="title" translatable="yes">_Data Roaming</property>
+ <property name="subtitle" translatable="yes">Use mobile data when roaming</property>
+ <signal name="notify::active" handler="wwan_data_settings_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <!-- Network Settings -->
+ <child>
+ <object class="GtkListBox" id="network_settings_list" >
+ <property name="visible">1</property>
+ <property name="margin-bottom">32</property>
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="frame" />
+ </style>
+ <signal name="row-activated" handler="wwan_network_settings_activated_cb" swapped="yes" />
+
+ <!-- Network Mode -->
+ <child>
+ <object class="CcListRow" id="network_mode_row">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="icon-name">go-next-symbolic</property>
+ <property name="title" translatable="yes">_Network Mode</property>
+ </object>
+ </child>
+
+ <!-- Network -->
+ <child>
+ <object class="CcListRow" id="network_name_row">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="icon-name">go-next-symbolic</property>
+ <property name="title" translatable="yes">N_etwork</property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+
+ <!-- Advanced Settings Title -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Advanced</property>
+ <property name="xalign">0.0</property>
+ <property name="margin-bottom">12</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+
+ <!-- Advanced settings -->
+ <child>
+ <object class="GtkListBox" id="advanced_settings_list" >
+ <property name="visible">1</property>
+ <property name="margin-bottom">32</property>
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="frame" />
+ </style>
+ <signal name="row-activated" handler="wwan_advanced_settings_activated_cb" swapped="yes" />
+
+ <!-- Accesss Point Settings -->
+ <child>
+ <object class="CcListRow" id="apn_settings_row">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="icon-name">go-next-symbolic</property>
+ <property name="title" translatable="yes">_Access Point Names</property>
+ </object>
+ </child>
+
+ <!-- SIM Lock -->
+ <child>
+ <object class="CcListRow" id="sim_lock_row">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="icon-name">go-next-symbolic</property>
+ <property name="title" translatable="yes">_SIM Lock</property>
+ <property name="subtitle" translatable="yes">Lock SIM with PIN</property>
+ </object>
+ </child>
+
+ <!-- Modem Details -->
+ <child>
+ <object class="CcListRow" id="details_row">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="icon-name">go-next-symbolic</property>
+ <property name="title" translatable="yes">M_odem Details</property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ </object>
+ <packing>
+ <property name="name">settings-view</property>
+ </packing>
+ </child>
+
+ </object> <!-- ./GtkStack main_stack -->
+ </child>
+ </template>
+ <object class="GtkSizeGroup">
+ <property name="mode">both</property>
+ <widgets>
+ <widget name="apn_settings_row"/>
+ <widget name="sim_lock_row"/>
+ <widget name="details_row"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/panels/wwan/cc-wwan-device.c b/panels/wwan/cc-wwan-device.c
new file mode 100644
index 000000000..31baff95c
--- /dev/null
+++ b/panels/wwan/cc-wwan-device.c
@@ -0,0 +1,1355 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-device.c
+ *
+ * Copyright 2019-2020 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wwan-device"
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib/gi18n.h>
+#include <polkit/polkit.h>
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+# include <NetworkManager.h>
+# include <nma-mobile-providers.h>
+#endif
+
+#include "cc-wwan-errors-private.h"
+#include "cc-wwan-device.h"
+
+/**
+ * @short_description: Device Object
+ * @include: "cc-wwan-device.h"
+ */
+
+struct _CcWwanDevice
+{
+ GObject parent_instance;
+
+ MMObject *mm_object;
+ MMModem *modem;
+ MMSim *sim;
+ MMModem3gpp *modem_3gpp;
+
+ const char *operator_code; /* MCCMNC */
+ GError *error;
+
+ /* Building with NetworkManager is optional,
+ * so #NMclient type can’t be used here.
+ */
+ GObject *nm_client; /* An #NMClient */
+ CcWwanData *wwan_data;
+
+ gulong modem_3gpp_id;
+ gulong modem_3gpp_locks_id;
+
+ /* Enabled locks like PIN, PIN2, PUK, etc. */
+ MMModem3gppFacility locks;
+
+ CcWwanState registration_state;
+ gboolean network_is_manual;
+};
+
+G_DEFINE_TYPE (CcWwanDevice, cc_wwan_device, G_TYPE_OBJECT)
+
+
+enum {
+ PROP_0,
+ PROP_OPERATOR_NAME,
+ PROP_ENABLED_LOCKS,
+ PROP_ERROR,
+ PROP_HAS_DATA,
+ PROP_NETWORK_MODE,
+ PROP_REGISTRATION_STATE,
+ PROP_SIGNAL,
+ PROP_UNLOCK_REQUIRED,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+cc_wwan_device_state_changed_cb (CcWwanDevice *self)
+{
+ MMModem3gppRegistrationState state;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_OPERATOR_NAME]);
+
+ state = mm_modem_3gpp_get_registration_state (self->modem_3gpp);
+
+ switch (state)
+ {
+ case MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN:
+ self->registration_state = CC_WWAN_REGISTRATION_STATE_UNKNOWN;
+ break;
+
+ case MM_MODEM_3GPP_REGISTRATION_STATE_DENIED:
+ self->registration_state = CC_WWAN_REGISTRATION_STATE_DENIED;
+ break;
+
+ case MM_MODEM_3GPP_REGISTRATION_STATE_IDLE:
+ self->registration_state = CC_WWAN_REGISTRATION_STATE_IDLE;
+ break;
+
+ case MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING:
+ self->registration_state = CC_WWAN_REGISTRATION_STATE_SEARCHING;
+ break;
+
+ case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING:
+ self->registration_state = CC_WWAN_REGISTRATION_STATE_ROAMING;
+ break;
+
+ default:
+ self->registration_state = CC_WWAN_REGISTRATION_STATE_REGISTERED;
+ break;
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REGISTRATION_STATE]);
+}
+
+static void
+cc_wwan_device_locks_changed_cb (CcWwanDevice *self)
+{
+ self->locks = mm_modem_3gpp_get_enabled_facility_locks (self->modem_3gpp);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLED_LOCKS]);
+}
+
+static void
+cc_wwan_device_3gpp_changed_cb (CcWwanDevice *self)
+{
+ gulong handler_id = 0;
+
+ if (self->modem_3gpp_id)
+ g_signal_handler_disconnect (self->modem_3gpp, self->modem_3gpp_id);
+ self->modem_3gpp_id = 0;
+
+ if (self->modem_3gpp_locks_id)
+ g_signal_handler_disconnect (self->modem_3gpp, self->modem_3gpp_locks_id);
+ self->modem_3gpp_locks_id = 0;
+
+ g_clear_object (&self->modem_3gpp);
+ self->modem_3gpp = mm_object_get_modem_3gpp (self->mm_object);
+
+ if (self->modem_3gpp)
+ {
+ handler_id = g_signal_connect_object (self->modem_3gpp, "notify::registration-state",
+ G_CALLBACK (cc_wwan_device_state_changed_cb),
+ self, G_CONNECT_SWAPPED);
+ self->modem_3gpp_id = handler_id;
+
+ handler_id = g_signal_connect_object (self->modem_3gpp, "notify::enabled-facility-locks",
+ G_CALLBACK (cc_wwan_device_locks_changed_cb),
+ self, G_CONNECT_SWAPPED);
+ self->modem_3gpp_locks_id = handler_id;
+ cc_wwan_device_locks_changed_cb (self);
+ cc_wwan_device_state_changed_cb (self);
+ }
+}
+
+static void
+cc_wwan_device_signal_quality_changed_cb (CcWwanDevice *self)
+{
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SIGNAL]);
+}
+
+static void
+cc_wwan_device_mode_changed_cb (CcWwanDevice *self)
+{
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NETWORK_MODE]);
+}
+
+static void
+wwan_device_emit_data_changed (CcWwanDevice *self)
+{
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_DATA]);
+}
+
+static void
+cc_wwan_device_unlock_required_cb (CcWwanDevice *self)
+{
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UNLOCK_REQUIRED]);
+}
+
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+static void
+cc_wwan_device_nm_changed_cb (CcWwanDevice *self,
+ GParamSpec *pspec,
+ NMClient *client)
+{
+ gboolean nm_is_running;
+
+ nm_is_running = nm_client_get_nm_running (client);
+
+ if (!nm_is_running && self->wwan_data != NULL)
+ {
+ g_clear_object (&self->wwan_data);
+ wwan_device_emit_data_changed (self);
+ }
+}
+
+static void
+cc_wwan_device_nm_device_added_cb (CcWwanDevice *self,
+ NMDevice *nm_device)
+{
+ if (!NM_IS_DEVICE_MODEM (nm_device))
+ return;
+
+ if(!self->sim || !cc_wwan_device_is_nm_device (self, G_OBJECT (nm_device)))
+ return;
+
+ self->wwan_data = cc_wwan_data_new (self->mm_object,
+ NM_CLIENT (self->nm_client));
+
+ if (self->wwan_data)
+ {
+ g_signal_connect_object (self->wwan_data, "notify::enabled",
+ G_CALLBACK (wwan_device_emit_data_changed),
+ self, G_CONNECT_SWAPPED);
+ wwan_device_emit_data_changed (self);
+ }
+}
+#endif
+
+static void
+cc_wwan_device_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcWwanDevice *self = (CcWwanDevice *)object;
+ MMModemMode allowed, preferred;
+
+ switch (prop_id)
+ {
+ case PROP_OPERATOR_NAME:
+ g_value_set_string (value, cc_wwan_device_get_operator_name (self));
+ break;
+
+ case PROP_ERROR:
+ g_value_set_boolean (value, self->error != NULL);
+ break;
+
+ case PROP_HAS_DATA:
+ g_value_set_boolean (value, self->wwan_data != NULL);
+ break;
+
+ case PROP_ENABLED_LOCKS:
+ g_value_set_int (value, self->locks);
+ break;
+
+ case PROP_NETWORK_MODE:
+ if (cc_wwan_device_get_current_mode (self, &allowed, &preferred))
+ g_value_take_string (value, cc_wwan_device_get_string_from_mode (self, allowed, preferred));
+ break;
+
+ case PROP_REGISTRATION_STATE:
+ g_value_set_int (value, self->registration_state);
+ break;
+
+ case PROP_UNLOCK_REQUIRED:
+ g_value_set_int (value, cc_wwan_device_get_lock (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wwan_device_dispose (GObject *object)
+{
+ CcWwanDevice *self = (CcWwanDevice *)object;
+
+ g_clear_error (&self->error);
+ g_clear_object (&self->modem);
+ g_clear_object (&self->mm_object);
+ g_clear_object (&self->sim);
+ g_clear_object (&self->modem_3gpp);
+
+ g_clear_object (&self->nm_client);
+ g_clear_object (&self->wwan_data);
+
+ G_OBJECT_CLASS (cc_wwan_device_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_device_class_init (CcWwanDeviceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = cc_wwan_device_get_property;
+ object_class->dispose = cc_wwan_device_dispose;
+
+ properties[PROP_OPERATOR_NAME] =
+ g_param_spec_string ("operator-name",
+ "Operator Name",
+ "Operator Name the device is connected to",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_ENABLED_LOCKS] =
+ g_param_spec_int ("enabled-locks",
+ "Enabled Locks",
+ "Locks Enabled in Modem",
+ MM_MODEM_3GPP_FACILITY_NONE,
+ MM_MODEM_3GPP_FACILITY_CORP_PERS,
+ MM_MODEM_3GPP_FACILITY_NONE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_ERROR] =
+ g_param_spec_boolean ("error",
+ "Error",
+ "Set if some Error occurs",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_HAS_DATA] =
+ g_param_spec_boolean ("has-data",
+ "has-data",
+ "Data for the device",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_NETWORK_MODE] =
+ g_param_spec_string ("network-mode",
+ "Network Mode",
+ "A String representing preferred network mode",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_REGISTRATION_STATE] =
+ g_param_spec_int ("registration-state",
+ "Registration State",
+ "The current network registration state",
+ CC_WWAN_REGISTRATION_STATE_UNKNOWN,
+ CC_WWAN_REGISTRATION_STATE_DENIED,
+ CC_WWAN_REGISTRATION_STATE_UNKNOWN,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_UNLOCK_REQUIRED] =
+ g_param_spec_int ("unlock-required",
+ "Unlock Required",
+ "The Modem lock status changed",
+ MM_MODEM_LOCK_UNKNOWN,
+ MM_MODEM_LOCK_PH_NETSUB_PUK,
+ MM_MODEM_LOCK_UNKNOWN,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_SIGNAL] =
+ g_param_spec_int ("signal",
+ "Signal",
+ "Get Device Signal",
+ 0, 100, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+cc_wwan_device_init (CcWwanDevice *self)
+{
+}
+
+/**
+ * cc_wwan_device_new:
+ * @mm_object: (transfer full): An #MMObject
+ *
+ * Create a new device representing the given
+ * @mm_object.
+ *
+ * Returns: A #CcWwanDevice
+ */
+CcWwanDevice *
+cc_wwan_device_new (MMObject *mm_object,
+ GObject *nm_client)
+{
+ CcWwanDevice *self;
+
+ g_return_val_if_fail (MM_IS_OBJECT (mm_object), NULL);
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+ g_return_val_if_fail (NM_IS_CLIENT (nm_client), NULL);
+#else
+ g_return_val_if_fail (!nm_client, NULL);
+#endif
+
+ self = g_object_new (CC_TYPE_WWAN_DEVICE, NULL);
+
+ self->mm_object = g_object_ref (mm_object);
+ self->modem = mm_object_get_modem (mm_object);
+ self->sim = mm_modem_get_sim_sync (self->modem, NULL, NULL);
+ g_set_object (&self->nm_client, nm_client);
+ if (self->sim)
+ {
+ self->operator_code = mm_sim_get_operator_identifier (self->sim);
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+ self->wwan_data = cc_wwan_data_new (mm_object,
+ NM_CLIENT (self->nm_client));
+#endif
+ }
+
+ g_signal_connect_object (self->mm_object, "notify::unlock-required",
+ G_CALLBACK (cc_wwan_device_unlock_required_cb),
+ self, G_CONNECT_SWAPPED);
+ if (self->wwan_data)
+ g_signal_connect_object (self->wwan_data, "notify::enabled",
+ G_CALLBACK (wwan_device_emit_data_changed),
+ self, G_CONNECT_SWAPPED);
+
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+ g_signal_connect_object (self->nm_client, "notify::nm-running" ,
+ G_CALLBACK (cc_wwan_device_nm_changed_cb), self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->nm_client, "device-added",
+ G_CALLBACK (cc_wwan_device_nm_device_added_cb),
+ self, G_CONNECT_SWAPPED);
+#endif
+
+ g_signal_connect_object (self->mm_object, "notify::modem3gpp",
+ G_CALLBACK (cc_wwan_device_3gpp_changed_cb),
+ self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->modem, "notify::signal-quality",
+ G_CALLBACK (cc_wwan_device_signal_quality_changed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ cc_wwan_device_3gpp_changed_cb (self);
+ g_signal_connect_object (self->modem, "notify::current-modes",
+ G_CALLBACK (cc_wwan_device_mode_changed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ return self;
+}
+
+gboolean
+cc_wwan_device_has_sim (CcWwanDevice *self)
+{
+ MMModemStateFailedReason state_reason;
+
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+ state_reason = mm_modem_get_state_failed_reason (self->modem);
+
+ if (state_reason == MM_MODEM_STATE_FAILED_REASON_SIM_MISSING)
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * cc_wwan_device_get_lock:
+ * @self: a #CcWwanDevice
+ *
+ * Get the active device lock that is required to
+ * be unlocked for accessing device features.
+ *
+ * Returns: %TRUE if PIN enabled, %FALSE otherwise.
+ */
+MMModemLock
+cc_wwan_device_get_lock (CcWwanDevice *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), MM_MODEM_LOCK_UNKNOWN);
+
+ return mm_modem_get_unlock_required (self->modem);
+}
+
+
+/**
+ * cc_wwan_device_get_sim_lock:
+ * @self: a #CcWwanDevice
+ *
+ * Get if SIM lock with PIN is enabled. SIM PIN
+ * enabled doesn’t mean that SIM is locked.
+ * See cc_wwan_device_get_lock().
+ *
+ * Returns: %TRUE if PIN enabled, %FALSE otherwise.
+ */
+gboolean
+cc_wwan_device_get_sim_lock (CcWwanDevice *self)
+{
+ gboolean sim_lock;
+
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+ sim_lock = self->locks & MM_MODEM_3GPP_FACILITY_SIM;
+
+ return !!sim_lock;
+}
+
+guint
+cc_wwan_device_get_unlock_retries (CcWwanDevice *self,
+ MMModemLock lock)
+{
+ MMUnlockRetries *retries;
+
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), 0);
+
+ retries = mm_modem_get_unlock_retries (self->modem);
+
+ return mm_unlock_retries_get (retries, lock);
+}
+
+static void
+cc_wwan_device_pin_sent_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanDevice *self;
+ MMSim *sim = (MMSim *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_sim_send_pin_finish (sim, result, &error))
+ {
+ self = g_task_get_source_object (G_TASK (task));
+
+ g_clear_error (&self->error);
+ self->error = g_error_copy (error);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ g_task_return_boolean (task, TRUE);
+ }
+}
+
+void
+cc_wwan_device_send_pin (CcWwanDevice *self,
+ const gchar *pin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+ g_return_if_fail (MM_IS_SIM (self->sim));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (pin && *pin);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ mm_sim_send_pin (self->sim, pin, cancellable,
+ cc_wwan_device_pin_sent_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_send_pin_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_puk_sent_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanDevice *self;
+ MMSim *sim = (MMSim *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_sim_send_puk_finish (sim, result, &error))
+ {
+ self = g_task_get_source_object (G_TASK (task));
+
+ g_clear_error (&self->error);
+ self->error = g_error_copy (error);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ g_task_return_boolean (task, TRUE);
+ }
+}
+
+void
+cc_wwan_device_send_puk (CcWwanDevice *self,
+ const gchar *puk,
+ const gchar *pin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+ g_return_if_fail (MM_IS_SIM (self->sim));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (puk && *puk);
+ g_return_if_fail (pin && *pin);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ mm_sim_send_puk (self->sim, puk, pin, cancellable,
+ cc_wwan_device_puk_sent_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_send_puk_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_enable_pin_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanDevice *self;
+ MMSim *sim = (MMSim *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_sim_enable_pin_finish (sim, result, &error))
+ {
+ self = g_task_get_source_object (G_TASK (task));
+
+ g_clear_error (&self->error);
+ self->error = g_error_copy (error);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ g_task_return_boolean (task, TRUE);
+ }
+}
+
+void
+cc_wwan_device_enable_pin (CcWwanDevice *self,
+ const gchar *pin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (pin && *pin);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ mm_sim_enable_pin (self->sim, pin, cancellable,
+ cc_wwan_device_enable_pin_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_enable_pin_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_disable_pin_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanDevice *self;
+ MMSim *sim = (MMSim *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_sim_disable_pin_finish (sim, result, &error))
+ {
+ self = g_task_get_source_object (G_TASK (task));
+
+ g_clear_error (&self->error);
+ self->error = g_error_copy (error);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ g_task_return_boolean (task, TRUE);
+ }
+}
+
+void
+cc_wwan_device_disable_pin (CcWwanDevice *self,
+ const gchar *pin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (pin && *pin);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ mm_sim_disable_pin (self->sim, pin, cancellable,
+ cc_wwan_device_disable_pin_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_disable_pin_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_change_pin_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanDevice *self;
+ MMSim *sim = (MMSim *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_sim_change_pin_finish (sim, result, &error))
+ {
+ self = g_task_get_source_object (G_TASK (task));
+
+ g_clear_error (&self->error);
+ self->error = g_error_copy (error);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ g_task_return_boolean (task, TRUE);
+ }
+}
+
+void
+cc_wwan_device_change_pin (CcWwanDevice *self,
+ const gchar *old_pin,
+ const gchar *new_pin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (old_pin && *old_pin);
+ g_return_if_fail (new_pin && *new_pin);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ mm_sim_change_pin (self->sim, old_pin, new_pin, cancellable,
+ cc_wwan_device_change_pin_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_change_pin_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_network_mode_set_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanDevice *self;
+ MMModem *modem = (MMModem *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_modem_set_current_modes_finish (modem, result, &error))
+ {
+ self = g_task_get_source_object (G_TASK (task));
+
+ g_clear_error (&self->error);
+ self->error = g_error_copy (error);
+ g_warning ("Error: %s", error->message);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ g_task_return_boolean (task, TRUE);
+ }
+}
+
+/**
+ * cc_wwan_device_set_network_mode:
+ * @self: a #CcWwanDevice
+ * @allowed: The allowed #MMModemModes
+ * @preferred: The preferred #MMModemMode
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: (nullable): a #GAsyncReadyCallback or %NULL
+ * @user_data: (nullable): closure data for @callback
+ *
+ * Asynchronously set preferred network mode.
+ *
+ * Call @cc_wwan_device_set_current_mode_finish()
+ * in @callback to get the result of operation.
+ */
+void
+cc_wwan_device_set_current_mode (CcWwanDevice *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+ GPermission *permission;
+ g_autoptr(GError) error = NULL;
+
+ g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ permission = polkit_permission_new_sync ("org.freedesktop.ModemManager1.Device.Control",
+ NULL, cancellable, &error);
+ if (permission)
+ g_task_set_task_data (task, permission, g_object_unref);
+
+ if (error)
+ g_warning ("error: %s", error->message);
+
+ if (error)
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else if (!g_permission_get_allowed (permission))
+ {
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_PERMISSION_DENIED,
+ "Access Denied");
+ g_clear_error (&self->error);
+ self->error = g_error_copy (error);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ mm_modem_set_current_modes (self->modem, allowed, preferred,
+ cancellable, cc_wwan_device_network_mode_set_cb,
+ g_steal_pointer (&task));
+ }
+}
+
+/**
+ * cc_wwan_device_set_current_mode_finish:
+ * @self: a #CcWwanDevice
+ * @result: a #GAsyncResult
+ * @error: a location for #GError or %NULL
+ *
+ * Get the status whether setting network mode
+ * succeeded
+ *
+ * Returns: %TRUE if network mode was successfully set,
+ * %FALSE otherwise.
+ */
+gboolean
+cc_wwan_device_set_current_mode_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+gboolean
+cc_wwan_device_get_current_mode (CcWwanDevice *self,
+ MMModemMode *allowed,
+ MMModemMode *preferred)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+ return mm_modem_get_current_modes (self->modem, allowed, preferred);
+}
+
+gboolean
+cc_wwan_device_is_auto_network (CcWwanDevice *self)
+{
+ /*
+ * XXX: ModemManager Doesn’t have a true API to check
+ * if registration is automatic or manual. So Let’s
+ * do some guess work.
+ */
+ if (self->registration_state == CC_WWAN_REGISTRATION_STATE_DENIED)
+ return FALSE;
+
+ return !self->network_is_manual;
+}
+
+CcWwanState
+cc_wwan_device_get_network_state (CcWwanDevice *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), 0);
+
+ return self->registration_state;
+}
+
+gboolean
+cc_wwan_device_get_supported_modes (CcWwanDevice *self,
+ MMModemMode *allowed,
+ MMModemMode *preferred)
+{
+ g_autofree MMModemModeCombination *modes = NULL;
+ guint n_modes, i;
+
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+ if (!mm_modem_get_supported_modes (self->modem, &modes, &n_modes))
+ return FALSE;
+
+ if (allowed)
+ *allowed = 0;
+ if (preferred)
+ *preferred = 0;
+
+ for (i = 0; i < n_modes; i++)
+ {
+ if (allowed)
+ *allowed = *allowed | modes[i].allowed;
+ if (preferred)
+ *preferred = *preferred | modes[i].preferred;
+ }
+
+ return TRUE;
+}
+
+#define APPEND_MODE_TO_STRING(_str, _now, _preferred, _mode_str) do { \
+ if (_str->len > 0) \
+ g_string_append (_str, ", "); \
+ g_string_append (_str, _mode_str); \
+ if (_preferred == _now) \
+ g_string_append (_str, _(" (Preferred)")); \
+ } while (0)
+
+gchar *
+cc_wwan_device_get_string_from_mode (CcWwanDevice *self,
+ MMModemMode allowed,
+ MMModemMode preferred)
+{
+ GString *str;
+
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+ g_return_val_if_fail (allowed != 0, NULL);
+
+ str = g_string_sized_new (10);
+
+ if (allowed & MM_MODEM_MODE_2G)
+ APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_2G, preferred, "2G");
+ if (allowed & MM_MODEM_MODE_3G)
+ APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_3G, preferred, "3G");
+ if (allowed & MM_MODEM_MODE_4G)
+ APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_4G, preferred, "4G");
+
+ if (allowed == MM_MODEM_MODE_2G ||
+ allowed == MM_MODEM_MODE_3G ||
+ allowed == MM_MODEM_MODE_4G)
+ g_string_append (str, _(" Only"));
+
+ if (str->len == 0)
+ return g_string_free (str, TRUE);
+ else
+ return g_string_free (str, FALSE);
+}
+#undef APPEND_MODE_TO_STRING
+
+static void
+wwan_network_list_free (GList *network_list)
+{
+ g_list_free_full (network_list, (GDestroyNotify)mm_modem_3gpp_network_free);
+}
+
+static void
+cc_wwan_device_scan_complete_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ MMModem3gpp *modem_3gpp = (MMModem3gpp *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+ GList *network_list;
+
+ network_list = mm_modem_3gpp_scan_finish (modem_3gpp, result, &error);
+
+ if (error)
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_pointer (task, network_list, (GDestroyNotify)wwan_network_list_free);
+}
+
+void
+cc_wwan_device_scan_networks (CcWwanDevice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ mm_modem_3gpp_scan (self->modem_3gpp, cancellable,
+ cc_wwan_device_scan_complete_cb,
+ g_steal_pointer (&task));
+}
+
+GList *
+cc_wwan_device_scan_networks_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_register_network_complete_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcWwanDevice *self;
+ MMModem3gpp *modem_3gpp = (MMModem3gpp *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_modem_3gpp_register_finish (modem_3gpp, result, &error))
+ {
+ self = g_task_get_source_object (G_TASK (task));
+
+ g_clear_error (&self->error);
+ self->error = g_error_copy (error);
+ g_warning ("Error: %s", error->message);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ g_task_return_boolean (task, TRUE);
+ }
+}
+
+void
+cc_wwan_device_register_network (CcWwanDevice *self,
+ const gchar *network_id,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ if (network_id && *network_id)
+ self->network_is_manual = TRUE;
+ else
+ self->network_is_manual = FALSE;
+
+ mm_modem_3gpp_register (self->modem_3gpp, network_id, cancellable,
+ cc_wwan_device_register_network_complete_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_register_network_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * cc_wwan_device_get_operator_name:
+ * @self: a #CcWwanDevice
+ *
+ * Get the human readable network operator name
+ * currently the device is connected to.
+ *
+ * Returns: (nullable): The operator name or %NULL
+ */
+const gchar *
+cc_wwan_device_get_operator_name (CcWwanDevice *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+ if (!self->modem_3gpp)
+ return NULL;
+
+ return mm_modem_3gpp_get_operator_name (self->modem_3gpp);
+}
+
+gchar *
+cc_wwan_device_dup_own_numbers (CcWwanDevice *self)
+{
+ const char *const *own_numbers;
+
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+ own_numbers = mm_modem_get_own_numbers (self->modem);
+
+ if (!own_numbers)
+ return NULL;
+
+ return g_strjoinv ("\n", (char **)own_numbers);
+}
+
+gchar *
+cc_wwan_device_dup_network_type_string (CcWwanDevice *self)
+{
+ MMModemAccessTechnology type;
+
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+ type = mm_modem_get_access_technologies (self->modem);
+
+ return mm_modem_access_technology_build_string_from_mask (type);
+}
+
+gchar *
+cc_wwan_device_dup_signal_string (CcWwanDevice *self)
+{
+ MMModemSignal *modem_signal;
+ MMSignal *signal;
+ GString *str;
+ gdouble value;
+ gboolean recent;
+
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+ modem_signal = mm_object_peek_modem_signal (self->mm_object);
+
+ if (!modem_signal)
+ return g_strdup_printf ("%d%%", mm_modem_get_signal_quality (self->modem, &recent));
+
+ str = g_string_new ("");
+
+ /* Adapted from ModemManager mmcli-modem-signal.c */
+ signal = mm_modem_signal_peek_cdma (modem_signal);
+ if (signal)
+ {
+ if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "rssi: %.2g dBm ", value);
+ if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "ecio: %.2g dBm ", value);
+ }
+
+ signal = mm_modem_signal_peek_evdo (modem_signal);
+ if (signal)
+ {
+ if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "rssi: %.2g dBm ", value);
+ if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "ecio: %.2g dBm ", value);
+ if ((value = mm_signal_get_sinr (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "sinr: %.2g dB ", value);
+ if ((value = mm_signal_get_io (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "io: %.2g dBm ", value);
+ }
+
+ signal = mm_modem_signal_peek_gsm (modem_signal);
+ if (signal)
+ if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "rssi: %.2g dBm ", value);
+
+ signal = mm_modem_signal_peek_umts (modem_signal);
+ if (signal)
+ {
+ if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "rssi: %.2g dBm ", value);
+ if ((value = mm_signal_get_rscp (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "rscp: %.2g dBm ", value);
+ if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "ecio: %.2g dBm ", value);
+ }
+
+ signal = mm_modem_signal_peek_lte (modem_signal);
+ if (signal)
+ {
+ if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "rssi: %.2g dBm ", value);
+ if ((value = mm_signal_get_rsrq (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "rsrq: %.2g dB ", value);
+ if ((value = mm_signal_get_rsrp (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "rsrp: %.2g dBm ", value);
+ if ((value = mm_signal_get_snr (signal)) != MM_SIGNAL_UNKNOWN)
+ g_string_append_printf (str, "snr: %.2g dB ", value);
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+const gchar *
+cc_wwan_device_get_manufacturer (CcWwanDevice *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+ return mm_modem_get_manufacturer (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_model (CcWwanDevice *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+ return mm_modem_get_model (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_firmware_version (CcWwanDevice *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+ return mm_modem_get_revision (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_identifier (CcWwanDevice *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+ return mm_modem_get_equipment_identifier (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_simple_error (CcWwanDevice *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+ if (!self->error)
+ return NULL;
+
+ return cc_wwan_error_get_message (self->error);
+}
+
+gboolean
+cc_wwan_device_is_nm_device (CcWwanDevice *self,
+ GObject *nm_device)
+{
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+ g_return_val_if_fail (NM_IS_DEVICE (nm_device), FALSE);
+
+ return g_str_equal (mm_modem_get_primary_port (self->modem),
+ nm_device_get_iface (NM_DEVICE (nm_device)));
+#else
+ return FALSE;
+#endif
+}
+
+const gchar *
+cc_wwan_device_get_path (CcWwanDevice *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), "");
+
+ return mm_object_get_path (self->mm_object);
+}
+
+CcWwanData *
+cc_wwan_device_get_data (CcWwanDevice *self)
+{
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+ return self->wwan_data;
+}
+
+gboolean
+cc_wwan_device_pin_valid (const gchar *password,
+ MMModemLock lock)
+{
+ size_t len;
+
+ g_return_val_if_fail (lock == MM_MODEM_LOCK_SIM_PIN ||
+ lock == MM_MODEM_LOCK_SIM_PIN2 ||
+ lock == MM_MODEM_LOCK_SIM_PUK ||
+ lock == MM_MODEM_LOCK_SIM_PUK2, FALSE);
+ if (!password)
+ return FALSE;
+
+ len = strlen (password);
+
+ if (len < 4 || len > 8)
+ return FALSE;
+
+ if (strspn (password, "0123456789") != len)
+ return FALSE;
+
+ /*
+ * XXX: Can PUK code be something other than 8 digits?
+ * 3GPP standard seems mum on this
+ */
+ if (lock == MM_MODEM_LOCK_SIM_PUK ||
+ lock == MM_MODEM_LOCK_SIM_PUK2)
+ if (len != 8)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/panels/wwan/cc-wwan-device.h b/panels/wwan/cc-wwan-device.h
new file mode 100644
index 000000000..e484bcf30
--- /dev/null
+++ b/panels/wwan/cc-wwan-device.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-device.h
+ *
+ * Copyright 2019-2020 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <libmm-glib.h>
+
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+# include "cc-wwan-data.h"
+#endif
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ CC_WWAN_REGISTRATION_STATE_UNKNOWN,
+ CC_WWAN_REGISTRATION_STATE_IDLE,
+ CC_WWAN_REGISTRATION_STATE_REGISTERED,
+ CC_WWAN_REGISTRATION_STATE_ROAMING,
+ CC_WWAN_REGISTRATION_STATE_SEARCHING,
+ CC_WWAN_REGISTRATION_STATE_DENIED
+} CcWwanState;
+
+typedef struct _CcWwanData CcWwanData;
+
+#define CC_TYPE_WWAN_DEVICE (cc_wwan_device_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanDevice, cc_wwan_device, CC, WWAN_DEVICE, GObject)
+
+CcWwanDevice *cc_wwan_device_new (MMObject *mm_object,
+ GObject *nm_client);
+gboolean cc_wwan_device_has_sim (CcWwanDevice *self);
+MMModemLock cc_wwan_device_get_lock (CcWwanDevice *self);
+gboolean cc_wwan_device_get_sim_lock (CcWwanDevice *self);
+guint cc_wwan_device_get_unlock_retries (CcWwanDevice *self,
+ MMModemLock lock);
+void cc_wwan_device_enable_pin (CcWwanDevice *self,
+ const gchar *pin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean cc_wwan_device_enable_pin_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error);
+void cc_wwan_device_disable_pin (CcWwanDevice *self,
+ const gchar *pin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean cc_wwan_device_disable_pin_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error);
+void cc_wwan_device_send_pin (CcWwanDevice *self,
+ const gchar *pin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean cc_wwan_device_send_pin_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error);
+void cc_wwan_device_send_puk (CcWwanDevice *self,
+ const gchar *puk,
+ const gchar *pin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean cc_wwan_device_send_puk_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error);
+void cc_wwan_device_change_pin (CcWwanDevice *self,
+ const gchar *old_pin,
+ const gchar *new_pin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean cc_wwan_device_change_pin_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error);
+const gchar *cc_wwan_device_get_operator_name (CcWwanDevice *self);
+gchar *cc_wwan_device_dup_own_numbers (CcWwanDevice *self);
+gchar *cc_wwan_device_dup_network_type_string (CcWwanDevice *self);
+gchar *cc_wwan_device_dup_signal_string (CcWwanDevice *self);
+const gchar *cc_wwan_device_get_manufacturer (CcWwanDevice *self);
+const gchar *cc_wwan_device_get_model (CcWwanDevice *self);
+const gchar *cc_wwan_device_get_firmware_version (CcWwanDevice *self);
+const gchar *cc_wwan_device_get_identifier (CcWwanDevice *self);
+gboolean cc_wwan_device_get_current_mode (CcWwanDevice *self,
+ MMModemMode *allowed,
+ MMModemMode *preferred);
+gboolean cc_wwan_device_is_auto_network (CcWwanDevice *self);
+CcWwanState cc_wwan_device_get_network_state (CcWwanDevice *self);
+gboolean cc_wwan_device_get_supported_modes (CcWwanDevice *self,
+ MMModemMode *allowed,
+ MMModemMode *preferred);
+void cc_wwan_device_set_current_mode (CcWwanDevice *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean cc_wwan_device_set_current_mode_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error);
+gchar *cc_wwan_device_get_string_from_mode (CcWwanDevice *self,
+ MMModemMode allowed,
+ MMModemMode preferred);
+void cc_wwan_device_scan_networks (CcWwanDevice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GList *cc_wwan_device_scan_networks_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error);
+void cc_wwan_device_register_network (CcWwanDevice *self,
+ const gchar *network_id,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean cc_wwan_device_register_network_finish (CcWwanDevice *self,
+ GAsyncResult *result,
+ GError **error);
+const gchar *cc_wwan_device_get_simple_error (CcWwanDevice *self);
+GSList *cc_wwan_device_get_apn_list (CcWwanDevice *self);
+gboolean cc_wwan_device_is_nm_device (CcWwanDevice *self,
+ GObject *nm_device);
+const gchar *cc_wwan_device_get_path (CcWwanDevice *self);
+CcWwanData *cc_wwan_device_get_data (CcWwanDevice *self);
+gboolean cc_wwan_device_pin_valid (const gchar *password,
+ MMModemLock lock);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-errors-private.h b/panels/wwan/cc-wwan-errors-private.h
new file mode 100644
index 000000000..761b82f35
--- /dev/null
+++ b/panels/wwan/cc-wwan-errors-private.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-errors-private.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * Modified from mm-error-helpers.c from ModemManager
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <libmm-glib.h>
+
+typedef struct {
+ guint code;
+ const gchar *message;
+} ErrorTable;
+
+
+static ErrorTable me_errors[] = {
+ { MM_MOBILE_EQUIPMENT_ERROR_PHONE_FAILURE, N_("Phone failure") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NO_CONNECTION, N_("No connection to phone") },
+ { MM_MOBILE_EQUIPMENT_ERROR_LINK_RESERVED, N_("Phone-adaptor link reserved") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED, N_("Operation not allowed") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, N_("Operation not supported") },
+ { MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN, N_("PH-SIM PIN required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN, N_("PH-FSIM PIN required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK, N_("PH-FSIM PUK required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED, N_("SIM not inserted") },
+ { MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN, N_("SIM PIN required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK, N_("SIM PUK required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE, N_("SIM failure") },
+ { MM_MOBILE_EQUIPMENT_ERROR_SIM_BUSY, N_("SIM busy") },
+ { MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG, N_("SIM wrong") },
+ { MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD, N_("Incorrect password") },
+ { MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN2, N_("SIM PIN2 required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK2, N_("SIM PUK2 required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FULL, N_("Memory full") },
+ { MM_MOBILE_EQUIPMENT_ERROR_INVALID_INDEX, N_("Invalid index") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NOT_FOUND, N_("Not found") },
+ { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FAILURE, N_("Memory failure") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK, N_("No network service") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, N_("Network timeout") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED, N_("Network not allowed - emergency calls only") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN, N_("Network personalization PIN required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK, N_("Network personalization PUK required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN, N_("Network subset personalization PIN required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK, N_("Network subset personalization PUK required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN, N_("Service provider personalization PIN required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK, N_("Service provider personalization PUK required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN, N_("Corporate personalization PIN required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK, N_("Corporate personalization PUK required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, N_("Unknown error") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_MS, N_("Illegal MS") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_ME, N_("Illegal ME") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_NOT_ALLOWED, N_("GPRS services not allowed") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PLMN_NOT_ALLOWED, N_("PLMN not allowed") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_LOCATION_NOT_ALLOWED, N_("Location area not allowed") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ROAMING_NOT_ALLOWED, N_("Roaming not allowed in this location area") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUPPORTED, N_("Service option not supported") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUBSCRIBED, N_("Requested service option not subscribed") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_OUT_OF_ORDER, N_("Service option temporarily out of order") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_UNKNOWN, N_("Unspecified GPRS error") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PDP_AUTH_FAILURE, N_("PDP authentication failure") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_INVALID_MOBILE_CLASS, N_("Invalid mobile class") },
+};
+
+static inline const gchar *
+cc_wwan_error_get_message (GError *error)
+{
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return _("Action Cancelled");
+
+ if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED))
+ return _("Access denied");
+
+ if (error->domain != MM_MOBILE_EQUIPMENT_ERROR)
+ return error->message;
+
+ for (guint i = 0; i < G_N_ELEMENTS (me_errors); i++)
+ if (me_errors[i].code == error->code)
+ return _(me_errors[i].message);
+
+ return _("Unknown Error");
+}
diff --git a/panels/wwan/cc-wwan-mode-dialog.c b/panels/wwan/cc-wwan-mode-dialog.c
new file mode 100644
index 000000000..e5917a41c
--- /dev/null
+++ b/panels/wwan/cc-wwan-mode-dialog.c
@@ -0,0 +1,327 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-mode-dialog.c
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-network-mode-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "list-box-helper.h"
+#include "cc-wwan-mode-dialog.h"
+#include "cc-wwan-resources.h"
+
+/**
+ * @short_description: WWAN network type selection dialog
+ */
+
+#define CC_TYPE_WWAN_MODE_ROW (cc_wwan_mode_row_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanModeRow, cc_wwan_mode_row, CC, WWAN_MODE_ROW, GtkListBoxRow)
+
+struct _CcWwanModeDialog
+{
+ GtkDialog parent_instance;
+
+ CcWwanDevice *device;
+ GtkListBox *network_mode_list;
+ CcWwanModeRow *selected_row;
+
+ MMModemMode preferred;
+ MMModemMode allowed;
+ MMModemMode new_allowed;
+ MMModemMode new_preferred;
+};
+
+G_DEFINE_TYPE (CcWwanModeDialog, cc_wwan_mode_dialog, GTK_TYPE_DIALOG)
+
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+struct _CcWwanModeRow
+{
+ GtkListBoxRow parent_instance;
+ GtkImage *ok_emblem;
+ MMModemMode allowed;
+ MMModemMode preferred;
+};
+
+G_DEFINE_TYPE (CcWwanModeRow, cc_wwan_mode_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_wwan_mode_row_class_init (CcWwanModeRowClass *klass)
+{
+}
+
+static void
+cc_wwan_mode_row_init (CcWwanModeRow *row)
+{
+}
+
+static void
+cc_wwan_mode_changed_cb (CcWwanModeDialog *self,
+ CcWwanModeRow *row)
+{
+ g_assert (CC_IS_WWAN_MODE_DIALOG (self));
+ g_assert (CC_IS_WWAN_MODE_ROW (row));
+
+ if (row == self->selected_row)
+ return;
+
+ gtk_widget_show (GTK_WIDGET (row->ok_emblem));
+
+ if (self->selected_row)
+ gtk_widget_hide (GTK_WIDGET (self->selected_row->ok_emblem));
+
+ self->selected_row = row;
+}
+
+static void
+cc_wwan_mode_dialog_ok_clicked_cb (CcWwanModeDialog *self)
+{
+ g_assert (CC_IS_WWAN_MODE_DIALOG (self));
+
+ if (self->selected_row)
+ {
+ cc_wwan_device_set_current_mode (self->device,
+ self->selected_row->allowed,
+ self->selected_row->preferred,
+ NULL, NULL, NULL);
+ }
+ else
+ {
+ g_return_if_reached ();
+ }
+
+ gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static GtkWidget *
+cc_wwan_mode_dialog_row_new (CcWwanModeDialog *self,
+ MMModemMode allowed,
+ MMModemMode preferred)
+{
+ CcWwanModeRow *row;
+ GtkWidget *box, *label, *image;
+ g_autofree gchar *mode = NULL;
+
+ g_assert (CC_WWAN_MODE_DIALOG (self));
+
+ row = g_object_new (CC_TYPE_WWAN_MODE_ROW, NULL);
+ row->allowed = allowed;
+ row->preferred = preferred;
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_widget_show (box);
+ g_object_set (box, "margin", 18, NULL);
+ gtk_container_add (GTK_CONTAINER (row), box);
+
+ mode = cc_wwan_device_get_string_from_mode (self->device, allowed, preferred);
+ label = gtk_label_new (mode);
+ gtk_widget_show (label);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ /* image should be hidden by default */
+ image = gtk_image_new_from_icon_name ("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON);
+ gtk_container_add (GTK_CONTAINER (box), image);
+ row->ok_emblem = GTK_IMAGE (image);
+
+ return GTK_WIDGET (row);
+}
+
+static void
+cc_wwan_mode_dialog_update (CcWwanModeDialog *self)
+{
+ MMModemMode allowed;
+ MMModemMode modes[][2] = {
+ {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G},
+ {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, 0},
+ {MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G},
+ {MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, 0},
+ {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G},
+ {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, 0},
+ {MM_MODEM_MODE_4G, 0},
+ {MM_MODEM_MODE_3G, 0},
+ {MM_MODEM_MODE_2G, 0},
+ };
+ size_t i;
+
+ g_assert (CC_IS_WWAN_MODE_DIALOG (self));
+
+ if (!cc_wwan_device_get_supported_modes (self->device, &allowed, NULL))
+ {
+ g_warning ("No modes supported by modem");
+ return;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (modes); i++)
+ {
+ GtkWidget *row;
+
+ if ((modes[i][0] & allowed) != modes[i][0])
+ continue;
+
+ if (modes[i][1] && !(modes[i][1] & allowed))
+ continue;
+
+ row = cc_wwan_mode_dialog_row_new (self, modes[i][0], modes[i][1]);
+ gtk_widget_show (row);
+ gtk_container_add (GTK_CONTAINER (self->network_mode_list), row);
+ }
+}
+
+static void
+cc_wwan_mode_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_DEVICE:
+ self->device = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wwan_mode_dialog_constructed (GObject *object)
+{
+ CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (object);
+
+ G_OBJECT_CLASS (cc_wwan_mode_dialog_parent_class)->constructed (object);
+
+ if(!cc_wwan_device_get_current_mode (self->device, &self->allowed, &self->preferred))
+ g_warning ("Can't get allowed and preferred wwan modes");
+
+ cc_wwan_mode_dialog_update (self);
+}
+
+static void
+cc_wwan_mode_dialog_dispose (GObject *object)
+{
+ CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (object);
+
+ g_clear_object (&self->device);
+
+ G_OBJECT_CLASS (cc_wwan_mode_dialog_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_mode_dialog_update_mode (CcWwanModeRow *row,
+ CcWwanModeDialog *self)
+{
+ if (self->allowed == row->allowed && self->preferred == row->preferred)
+ {
+ self->selected_row = row;
+ gtk_widget_show (GTK_WIDGET (row->ok_emblem));
+ }
+ else
+ gtk_widget_hide (GTK_WIDGET (row->ok_emblem));
+}
+
+static void
+cc_wwan_mode_dialog_show (GtkWidget *widget)
+{
+ CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (widget);
+
+ if(!cc_wwan_device_get_current_mode (self->device, &self->allowed, &self->preferred))
+ {
+ g_warning ("Can't get allowed and preferred wwan modes");
+ goto end;
+ }
+
+ gtk_container_foreach (GTK_CONTAINER (self->network_mode_list),
+ (GtkCallback)cc_wwan_mode_dialog_update_mode,
+ self);
+ end:
+ GTK_WIDGET_CLASS (cc_wwan_mode_dialog_parent_class)->show (widget);
+}
+
+static void
+cc_wwan_mode_dialog_class_init (CcWwanModeDialogClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = cc_wwan_mode_dialog_set_property;
+ object_class->constructed = cc_wwan_mode_dialog_constructed;
+ object_class->dispose = cc_wwan_mode_dialog_dispose;
+
+ widget_class->show = cc_wwan_mode_dialog_show;
+
+ properties[PROP_DEVICE] =
+ g_param_spec_object ("device",
+ "Device",
+ "The WWAN Device",
+ CC_TYPE_WWAN_DEVICE,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/wwan/cc-wwan-mode-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWwanModeDialog, network_mode_list);
+
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_mode_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_mode_dialog_ok_clicked_cb);
+}
+
+static void
+cc_wwan_mode_dialog_init (CcWwanModeDialog *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_list_box_set_header_func (self->network_mode_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+}
+
+CcWwanModeDialog *
+cc_wwan_mode_dialog_new (GtkWindow *parent_window,
+ CcWwanDevice *device)
+{
+ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+ return g_object_new (CC_TYPE_WWAN_MODE_DIALOG,
+ "transient-for", parent_window,
+ "use-header-bar", 1,
+ "device", device,
+ NULL);
+}
diff --git a/panels/wwan/cc-wwan-mode-dialog.h b/panels/wwan/cc-wwan-mode-dialog.h
new file mode 100644
index 000000000..2399f0b7b
--- /dev/null
+++ b/panels/wwan/cc-wwan-mode-dialog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-mode-dialog.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <handy.h>
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_MODE_DIALOG (cc_wwan_mode_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanModeDialog, cc_wwan_mode_dialog, CC, WWAN_MODE_DIALOG, GtkDialog)
+
+CcWwanModeDialog *cc_wwan_mode_dialog_new (GtkWindow *parent_window,
+ CcWwanDevice *device);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-mode-dialog.ui b/panels/wwan/cc-wwan-mode-dialog.ui
new file mode 100644
index 000000000..e0a924a39
--- /dev/null
+++ b/panels/wwan/cc-wwan-mode-dialog.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcWwanModeDialog" parent="GtkDialog">
+ <property name="title" translatable="yes">Network Mode</property>
+ <property name="default-height">480</property>
+ <property name="default-width">360</property>
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkListBox" id="network_mode_list">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="cc_wwan_mode_changed_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child type="action">
+ <object class="GtkButton" id="button_cancel">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Cancel</property>
+ <signal name="clicked" handler="gtk_widget_hide" swapped="yes"/>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="button_ok">
+ <property name="visible">1</property>
+ <property name="can-default">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Set</property>
+ <signal name="clicked" handler="cc_wwan_mode_dialog_ok_clicked_cb" swapped="yes"/>
+ <style>
+ <class name="suggested-action "/>
+ </style>
+ </object>
+ </child>
+
+ <action-widgets>
+ <action-widget response="cancel">button_cancel</action-widget>
+ <action-widget response="apply" default="true">button_ok</action-widget>
+ </action-widgets>
+ </template>
+</interface>
diff --git a/panels/wwan/cc-wwan-network-dialog.c b/panels/wwan/cc-wwan-network-dialog.c
new file mode 100644
index 000000000..1c8883b88
--- /dev/null
+++ b/panels/wwan/cc-wwan-network-dialog.c
@@ -0,0 +1,443 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-network-dialog.c
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wwan-network-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "list-box-helper.h"
+#include "cc-list-row.h"
+#include "cc-wwan-errors-private.h"
+#include "cc-wwan-network-dialog.h"
+#include "cc-wwan-resources.h"
+
+/**
+ * @short_description: WWAN network operator selection dialog
+ */
+
+#define CC_TYPE_WWAN_NETWORK_ROW (cc_wwan_network_row_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanNetworkRow, cc_wwan_network_row, CC, WWAN_NETWORK_ROW, GtkListBoxRow)
+
+struct _CcWwanNetworkDialog
+{
+ GtkDialog parent_instance;
+
+ CcListRow *automatic_row;
+ GtkButton *button_apply;
+ GtkSpinner *loading_spinner;
+ GtkBox *network_search_title;
+ GtkLabel *notification_label;
+ GtkRevealer *notification_revealer;
+ GtkListBox *operator_list_box;
+ GtkButton *refresh_button;
+
+ CcWwanDevice *device;
+ GList *operator_list;
+
+ CcWwanNetworkRow *selected_row;
+
+ GCancellable *search_cancellable;
+
+ guint revealer_timeout_id;
+ gboolean no_update_network;
+};
+
+G_DEFINE_TYPE (CcWwanNetworkDialog, cc_wwan_network_dialog, GTK_TYPE_DIALOG)
+
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+struct _CcWwanNetworkRow
+{
+ GtkListBoxRow parent_instance;
+ GtkImage *ok_emblem;
+ gchar *operator_code;
+};
+
+G_DEFINE_TYPE (CcWwanNetworkRow, cc_wwan_network_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_wwan_network_row_finalize (GObject *object)
+{
+ CcWwanNetworkRow *row = (CcWwanNetworkRow *)object;
+
+ g_free (row->operator_code);
+
+ G_OBJECT_CLASS (cc_wwan_network_row_parent_class)->finalize (object);
+}
+
+static void
+cc_wwan_network_row_class_init (CcWwanNetworkRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = cc_wwan_network_row_finalize;
+}
+
+static void
+cc_wwan_network_row_init (CcWwanNetworkRow *row)
+{
+}
+
+static void
+cc_wwan_on_notification_closed (CcWwanNetworkDialog *self,
+ GtkWidget *button)
+{
+ g_assert (CC_IS_WWAN_NETWORK_DIALOG (self));
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->notification_revealer), FALSE);
+
+ if (self->revealer_timeout_id != 0)
+ g_source_remove (self->revealer_timeout_id);
+
+ self->revealer_timeout_id = 0;
+}
+
+static gboolean
+cc_wwan_on_notification_timeout (gpointer user_data)
+{
+ cc_wwan_on_notification_closed (user_data, NULL);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+cc_wwan_network_changed_cb (CcWwanNetworkDialog *self,
+ CcWwanNetworkRow *row)
+{
+ if (row == self->selected_row)
+ return;
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button_apply), TRUE);
+ gtk_widget_show (GTK_WIDGET (row->ok_emblem));
+
+ if (self->selected_row)
+ gtk_widget_hide (GTK_WIDGET (self->selected_row->ok_emblem));
+
+ self->selected_row = row;
+}
+
+/*
+ * cc_wwan_network_dialog_row_new:
+ * @self: a #CcWwanNetworkDialog
+ * @operator_name: (transfer full): The long operator name
+ * @operator_id: (transfer full): operator id
+ */
+static CcWwanNetworkRow *
+cc_wwan_network_dialog_row_new (CcWwanNetworkDialog *self,
+ const gchar *operator_name,
+ const gchar *operator_code)
+{
+ CcWwanNetworkRow *row;
+ GtkWidget *box, *label, *image;
+
+ row = g_object_new (CC_TYPE_WWAN_NETWORK_ROW, NULL);
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_widget_show (box);
+ g_object_set (box, "margin", 18, NULL);
+ gtk_container_add (GTK_CONTAINER (row), box);
+
+ label = gtk_label_new (operator_name);
+ gtk_widget_show (label);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ image = gtk_image_new_from_icon_name ("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON);
+ row->ok_emblem = GTK_IMAGE (image);
+ gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (row->ok_emblem));
+
+ row->operator_code = g_strdup (operator_code);
+
+ return row;
+}
+
+static void
+cc_wwan_network_dialog_update_current_network (CcWwanNetworkDialog *self)
+{
+ CcWwanNetworkRow *row;
+ const gchar *operator_name;
+
+ operator_name = cc_wwan_device_get_operator_name (self->device);
+
+ if (!operator_name || operator_name[0] == '\0')
+ return;
+
+ gtk_container_foreach (GTK_CONTAINER (self->operator_list_box),
+ (GtkCallback)gtk_widget_destroy, NULL);
+
+ row = cc_wwan_network_dialog_row_new (self, operator_name, "");
+ self->selected_row = row;
+ gtk_container_add (GTK_CONTAINER (self->operator_list_box), GTK_WIDGET (row));
+ gtk_widget_show_all (GTK_WIDGET (self->operator_list_box));
+}
+
+static void
+cc_wwan_network_dialog_update (CcWwanNetworkDialog *self)
+{
+ CcWwanNetworkRow *row;
+ GList *item;
+ const gchar *operator_code, *operator_name;
+
+ gtk_container_foreach (GTK_CONTAINER (self->operator_list_box),
+ (GtkCallback)gtk_widget_destroy, NULL);
+
+ for (item = self->operator_list; item; item = item->next)
+ {
+ operator_code = mm_modem_3gpp_network_get_operator_code (item->data);
+ operator_name = mm_modem_3gpp_network_get_operator_long (item->data);
+
+ row = cc_wwan_network_dialog_row_new (self, operator_name, operator_code);
+ gtk_widget_show (GTK_WIDGET (row));
+ gtk_container_add (GTK_CONTAINER (self->operator_list_box), GTK_WIDGET (row));
+ }
+}
+
+static void
+cc_wwan_network_scan_complete_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(CcWwanNetworkDialog) self = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (self->operator_list)
+ g_list_free_full (self->operator_list, (GDestroyNotify)mm_modem_3gpp_network_free);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->refresh_button), TRUE);
+ gtk_spinner_stop (self->loading_spinner);
+ self->operator_list = cc_wwan_device_scan_networks_finish (self->device, result, &error);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->operator_list_box), !error);
+
+ if (!error)
+ {
+ cc_wwan_network_dialog_update (self);
+ gtk_widget_show (GTK_WIDGET (self->operator_list_box));
+ }
+ else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ self->no_update_network = TRUE;
+ gtk_widget_activate (GTK_WIDGET (self->automatic_row));
+ gtk_widget_set_sensitive (GTK_WIDGET (self->operator_list_box), FALSE);
+
+ gtk_label_set_label (self->notification_label,
+ cc_wwan_error_get_message (error));
+ gtk_revealer_set_reveal_child (self->notification_revealer, TRUE);
+ self->revealer_timeout_id = g_timeout_add_seconds (5, cc_wwan_on_notification_timeout, self);
+
+ gtk_widget_show (GTK_WIDGET (self->operator_list_box));
+ g_warning ("Error: scanning networks failed: %s", error->message);
+ }
+}
+
+static void
+cc_wwan_network_dialog_refresh_networks (CcWwanNetworkDialog *self)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET (self->refresh_button), FALSE);
+ gtk_spinner_start (self->loading_spinner);
+ cc_wwan_device_scan_networks (self->device, self->search_cancellable,
+ (GAsyncReadyCallback)cc_wwan_network_scan_complete_cb,
+ g_object_ref (self));
+}
+
+static void
+cc_wwan_network_dialog_apply_clicked_cb (CcWwanNetworkDialog *self)
+{
+ gboolean is_auto;
+
+ g_assert (CC_IS_WWAN_NETWORK_DIALOG (self));
+
+ is_auto = cc_list_row_get_active (self->automatic_row);
+
+ if (is_auto)
+ cc_wwan_device_register_network (self->device, "", NULL, NULL, NULL);
+ else if (self->selected_row)
+ cc_wwan_device_register_network (self->device, self->selected_row->operator_code, NULL, NULL, self);
+ else
+ g_warn_if_reached ();
+
+ gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static void
+cc_wwan_auto_network_changed_cb (CcWwanNetworkDialog *self,
+ GParamSpec *pspec,
+ CcListRow *auto_network_row)
+{
+ gboolean is_auto;
+
+ g_assert (CC_IS_WWAN_NETWORK_DIALOG (self));
+ g_assert (CC_IS_LIST_ROW (auto_network_row));
+
+ is_auto = cc_list_row_get_active (auto_network_row);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button_apply), is_auto);
+
+ if (self->no_update_network)
+ {
+ self->no_update_network = FALSE;
+ return;
+ }
+
+ self->selected_row = NULL;
+ gtk_widget_set_visible (GTK_WIDGET (self->network_search_title), !is_auto);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->operator_list_box), !is_auto);
+ gtk_widget_hide (GTK_WIDGET (self->operator_list_box));
+
+ if (is_auto)
+ {
+ g_cancellable_cancel (self->search_cancellable);
+ g_cancellable_reset (self->search_cancellable);
+ }
+ else
+ {
+ cc_wwan_network_dialog_refresh_networks (self);
+ }
+}
+
+static void
+cc_wwan_network_dialog_show (GtkWidget *widget)
+{
+ CcWwanNetworkDialog *self = (CcWwanNetworkDialog *)widget;
+ gboolean is_auto;
+
+ is_auto = cc_wwan_device_is_auto_network (self->device);
+
+ g_object_set (self->automatic_row, "active", is_auto, NULL);
+
+ cc_wwan_network_dialog_update_current_network (self);
+
+ GTK_WIDGET_CLASS (cc_wwan_network_dialog_parent_class)->show (widget);
+}
+
+static void
+cc_wwan_network_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWwanNetworkDialog *self = (CcWwanNetworkDialog *)object;
+
+ switch (prop_id)
+ {
+ case PROP_DEVICE:
+ self->device = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wwan_network_dialog_dispose (GObject *object)
+{
+ CcWwanNetworkDialog *self = (CcWwanNetworkDialog *)object;
+
+ if (self->revealer_timeout_id != 0)
+ g_source_remove (self->revealer_timeout_id);
+
+ self->revealer_timeout_id = 0;
+
+ g_cancellable_cancel (self->search_cancellable);
+
+ g_clear_object (&self->search_cancellable);
+ g_clear_object (&self->device);
+
+ G_OBJECT_CLASS (cc_wwan_network_dialog_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_network_dialog_class_init (CcWwanNetworkDialogClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = cc_wwan_network_dialog_set_property;
+ object_class->dispose = cc_wwan_network_dialog_dispose;
+
+ widget_class->show = cc_wwan_network_dialog_show;
+
+ properties[PROP_DEVICE] =
+ g_param_spec_object ("device",
+ "Device",
+ "The WWAN Device",
+ CC_TYPE_WWAN_DEVICE,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/wwan/cc-wwan-network-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, automatic_row);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, button_apply);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, loading_spinner);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, network_search_title);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, notification_label);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, notification_revealer);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, operator_list_box);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, refresh_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_network_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_on_notification_closed);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_auto_network_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_network_dialog_refresh_networks);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_network_dialog_apply_clicked_cb);
+}
+
+static void
+cc_wwan_network_dialog_init (CcWwanNetworkDialog *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->search_cancellable = g_cancellable_new ();
+
+ gtk_list_box_set_header_func (self->operator_list_box,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+}
+
+CcWwanNetworkDialog *
+cc_wwan_network_dialog_new (GtkWindow *parent_window,
+ CcWwanDevice *device)
+{
+ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+ return g_object_new (CC_TYPE_WWAN_NETWORK_DIALOG,
+ "transient-for", parent_window,
+ "use-header-bar", 1,
+ "device", device,
+ NULL);
+}
diff --git a/panels/wwan/cc-wwan-network-dialog.h b/panels/wwan/cc-wwan-network-dialog.h
new file mode 100644
index 000000000..1818a0876
--- /dev/null
+++ b/panels/wwan/cc-wwan-network-dialog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-network-dialog.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <handy.h>
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_NETWORK_DIALOG (cc_wwan_network_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanNetworkDialog, cc_wwan_network_dialog, CC, WWAN_NETWORK_DIALOG, GtkDialog)
+
+CcWwanNetworkDialog *cc_wwan_network_dialog_new (GtkWindow *parent_window,
+ CcWwanDevice *device);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-network-dialog.ui b/panels/wwan/cc-wwan-network-dialog.ui
new file mode 100644
index 000000000..03223b333
--- /dev/null
+++ b/panels/wwan/cc-wwan-network-dialog.ui
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcWwanNetworkDialog" parent="GtkDialog">
+ <property name="title" translatable="yes">Network</property>
+ <property name="default-height">480</property>
+ <property name="default-width">360</property>
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="border-width">0</property>
+ <property name="width-request">340</property>
+ <property name="height-request">360</property>
+ <child>
+ <object class="GtkOverlay">
+ <property name="visible">1</property>
+ <child type="overlay">
+ <object class="GtkRevealer" id="notification_revealer">
+ <property name="visible">1</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="spacing">12</property>
+ <style>
+ <class name="frame" />
+ <class name="app-notification" />
+ </style>
+ <child>
+ <object class="GtkLabel" id="notification_label">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="use-markup">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <signal name="clicked" handler="cc_wwan_on_notification_closed" swapped="yes" />
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Close</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">window-close-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="orientation">vertical</property>
+
+ <!-- Automatic Network Selection Switch -->
+ <child>
+ <object class="GtkListBox">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <property name="margin-bottom">18</property>
+ <style>
+ <class name="frame" />
+ </style>
+ <child>
+ <object class="CcListRow" id="automatic_row">
+ <property name="visible">1</property>
+ <property name="show-switch">1</property>
+ <property name="use-underline">1</property>
+ <property name="title" translatable="yes">_Automatic</property>
+ <signal name="notify::active" handler="cc_wwan_auto_network_changed_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Network Selection List Title and Spinner -->
+ <child>
+ <object class="GtkBox" id="network_search_title" >
+ <property name="visible">1</property>
+ <property name="margin-bottom">9</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Choose Network</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="loading_spinner">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="refresh_button">
+ <property name="visible">1</property>
+ <signal name="clicked" handler="cc_wwan_network_dialog_refresh_networks" swapped="yes" />
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Refresh Network Providers</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">view-refresh-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+
+ <!-- Network Selection List -->
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">1</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="propagate-natural-height">1</property>
+ <child>
+ <object class="GtkListBox" id="operator_list_box">
+ <property name="visible">0</property>
+ <property name="sensitive">0</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="cc_wwan_network_changed_cb" swapped="yes" />
+ <style>
+ <class name="frame" />
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child> <!-- ./internal-child -->
+
+ <child type="action">
+ <object class="GtkButton" id="button_cancel">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Cancel</property>
+ <signal name="clicked" handler="gtk_widget_hide" swapped="yes"/>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="button_apply">
+ <property name="visible">1</property>
+ <property name="can-default">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Set</property>
+ <signal name="clicked" handler="cc_wwan_network_dialog_apply_clicked_cb" swapped="yes"/>
+ <style>
+ <class name="suggested-action "/>
+ </style>
+ </object>
+ </child>
+
+ <action-widgets>
+ <action-widget response="cancel">button_cancel</action-widget>
+ <action-widget response="apply" default="true">button_apply</action-widget>
+ </action-widgets>
+ </template>
+</interface>
diff --git a/panels/wwan/cc-wwan-panel.c b/panels/wwan/cc-wwan-panel.c
new file mode 100644
index 000000000..963c46900
--- /dev/null
+++ b/panels/wwan/cc-wwan-panel.c
@@ -0,0 +1,929 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-panel.c
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wwan-panel"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "cc-wwan-device.h"
+#include "cc-wwan-data.h"
+#include "cc-wwan-device-page.h"
+#include "cc-wwan-panel.h"
+#include "cc-wwan-resources.h"
+
+#include "shell/cc-application.h"
+#include "shell/cc-debug.h"
+#include "shell/cc-object-storage.h"
+
+typedef enum {
+ OPERATION_NULL,
+ OPERATION_SHOW_DEVICE,
+} CmdlineOperation;
+
+struct _CcWwanPanel
+{
+ CcPanel parent_instance;
+
+ GtkListBox *data_select_listbox;
+ GtkPopover *data_select_popover;
+ GtkLabel *data_sim_label;
+ GtkListBox *data_sim_select_listbox;
+ GtkStack *devices_stack;
+ GtkStackSwitcher *devices_switcher;
+ GtkSwitch *enable_switch;
+ GtkStack *main_stack;
+ GtkRevealer *multi_device_revealer;
+ GtkLabel *notification_label;
+ GtkRevealer *notification_revealer;
+
+ GDBusProxy *rfkill_proxy;
+ MMManager *mm_manager;
+ NMClient *nm_client;
+
+ /* The default device that will be used for data */
+ CcWwanDevice *data_device;
+ GListStore *devices;
+ GListStore *data_devices;
+ GCancellable *cancellable;
+
+ CmdlineOperation arg_operation;
+ char *arg_device;
+
+ guint revealer_timeout_id;
+};
+
+enum {
+ PROP_0,
+ PROP_PARAMETERS
+};
+
+G_DEFINE_TYPE (CcWwanPanel, cc_wwan_panel, CC_TYPE_PANEL)
+
+
+#define CC_TYPE_DATA_DEVICE_ROW (cc_data_device_row_get_type())
+G_DECLARE_FINAL_TYPE (CcDataDeviceRow, cc_data_device_row, CC, DATA_DEVICE_ROW, GtkListBoxRow)
+
+struct _CcDataDeviceRow
+{
+ GtkListBoxRow parent_instance;
+
+ GtkImage *ok_emblem;
+ CcWwanDevice *device;
+};
+
+G_DEFINE_TYPE (CcDataDeviceRow, cc_data_device_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_data_device_row_class_init (CcDataDeviceRowClass *klass)
+{
+}
+
+static void
+cc_data_device_row_init (CcDataDeviceRow *row)
+{
+}
+
+static CmdlineOperation
+cmdline_operation_from_string (const gchar *str)
+{
+ if (g_strcmp0 (str, "show-device") == 0)
+ return OPERATION_SHOW_DEVICE;
+
+ g_warning ("Invalid additional argument %s", str);
+ return OPERATION_NULL;
+}
+
+static void
+reset_command_line_args (CcWwanPanel *self)
+{
+ self->arg_operation = OPERATION_NULL;
+ g_clear_pointer (&self->arg_device, g_free);
+}
+
+static gboolean
+verify_argv (CcWwanPanel *self,
+ const char **args)
+{
+ switch (self->arg_operation)
+ {
+ case OPERATION_SHOW_DEVICE:
+ if (self->arg_device == NULL)
+ {
+ g_warning ("Operation %s requires an object path", args[0]);
+ return FALSE;
+ }
+ default:
+ return TRUE;
+ }
+}
+
+static void
+handle_argv (CcWwanPanel *self)
+{
+ if (self->arg_operation == OPERATION_SHOW_DEVICE &&
+ self->arg_operation)
+ {
+ g_autoptr(GList) pages = NULL;
+
+ pages = gtk_container_get_children (GTK_CONTAINER (self->devices_stack));
+
+ for (GList *page = pages; page; page = page->next)
+ {
+ CcWwanDevice *device;
+
+ device = cc_wwan_device_page_get_device (page->data);
+
+ if (g_strcmp0 (cc_wwan_device_get_path (device), self->arg_device) == 0)
+ {
+ gtk_stack_set_visible_child (GTK_STACK (self->devices_stack), page->data);
+ g_debug ("Opening device %s", self->arg_device);
+ reset_command_line_args (self);
+ return;
+ }
+ }
+ }
+}
+
+static gboolean
+wwan_panel_device_is_supported (GDBusObject *object)
+{
+ MMObject *mm_object;
+ MMModem *modem;
+ MMModemCapability capability;
+
+ g_assert (G_IS_DBUS_OBJECT (object));
+
+ mm_object = MM_OBJECT (object);
+ modem = mm_object_get_modem (mm_object);
+ capability = mm_modem_get_current_capabilities (modem);
+
+ /* We Support only GSM/3G/LTE devices */
+ if (capability & (MM_MODEM_CAPABILITY_GSM_UMTS |
+ MM_MODEM_CAPABILITY_LTE |
+ MM_MODEM_CAPABILITY_LTE_ADVANCED))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gint
+wwan_model_get_item_index (GListModel *model,
+ gpointer item)
+{
+ guint i, n_items;
+
+ g_assert (G_IS_LIST_MODEL (model));
+ g_assert (G_IS_OBJECT (item));
+
+ n_items = g_list_model_get_n_items (model);
+
+ for (i = 0; i < n_items; i++)
+ {
+ g_autoptr(GObject) object = NULL;
+
+ object = g_list_model_get_item (model, i);
+
+ if (object == item)
+ return i;
+ }
+
+ return -1;
+}
+
+static CcWwanDevice *
+wwan_model_get_item_from_mm_object (GListModel *model,
+ MMObject *mm_object)
+{
+ const gchar *modem_path, *device_path;
+ guint i, n_items;
+
+ n_items = g_list_model_get_n_items (model);
+ modem_path = mm_object_get_path (mm_object);
+
+ for (i = 0; i < n_items; i++)
+ {
+ g_autoptr(CcWwanDevice) device = NULL;
+
+ device = g_list_model_get_item (model, i);
+ device_path = cc_wwan_device_get_path (device);
+
+ if (g_str_equal (modem_path, device_path))
+ return g_steal_pointer (&device);
+ }
+
+ return NULL;
+}
+
+static CcDataDeviceRow *
+cc_data_device_row_new (CcWwanDevice *device,
+ CcWwanPanel *self)
+{
+ CcDataDeviceRow *row;
+ GtkWidget *box, *label, *image;
+ g_autofree gchar *operator = NULL;
+ gint index;
+
+ row = g_object_new (CC_TYPE_DATA_DEVICE_ROW, NULL);
+ row->device = device;
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_widget_show (box);
+ g_object_set (box, "margin", 12, NULL);
+ gtk_container_add (GTK_CONTAINER (row), box);
+
+ index = wwan_model_get_item_index (G_LIST_MODEL (self->devices), device);
+ operator = g_strdup_printf ("SIM %d", index + 1);
+ label = gtk_label_new (operator);
+ gtk_widget_show (label);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ image = gtk_image_new_from_icon_name ("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON);
+ row->ok_emblem = GTK_IMAGE (image);
+ gtk_container_add (GTK_CONTAINER (box), image);
+
+ return row;
+}
+
+static void
+wwan_notification_close_clicked_cb (CcWwanPanel *self)
+{
+ gtk_revealer_set_reveal_child (self->notification_revealer, FALSE);
+
+ if (self->revealer_timeout_id != 0)
+ g_source_remove (self->revealer_timeout_id);
+
+ self->revealer_timeout_id = 0;
+}
+
+static void
+wwan_data_selector_clicked_cb (CcWwanPanel *self)
+{
+ if (gtk_widget_is_visible (GTK_WIDGET (self->data_select_popover)))
+ gtk_popover_popdown (self->data_select_popover);
+ else
+ gtk_popover_popup (self->data_select_popover);
+}
+
+static void
+cc_wwan_panel_update_data_selection (CcDataDeviceRow *row,
+ CcWwanPanel *self)
+{
+ if (self->data_device == row->device)
+ {
+ g_autofree gchar *str = NULL;
+ gint i;
+
+ i = wwan_model_get_item_index (G_LIST_MODEL (self->devices), row->device);
+ g_assert (i >= 0);
+
+ /* Human index starts from 1 */
+ str = g_strdup_printf ("SIM %d", i + 1);
+ gtk_label_set_label (self->data_sim_label, str);
+
+ gtk_widget_show (GTK_WIDGET (row->ok_emblem));
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (row->ok_emblem));
+ }
+}
+
+static void
+cc_wwan_data_item_activate_cb (CcWwanPanel *self,
+ CcDataDeviceRow *row)
+{
+ CcWwanData *data;
+
+ gtk_popover_popdown (self->data_select_popover);
+
+ if (row->device == self->data_device)
+ return;
+
+ /* Set lower priority for previously selected APN */
+ data = cc_wwan_device_get_data (self->data_device);
+ cc_wwan_data_set_priority (data, CC_WWAN_APN_PRIORITY_LOW);
+ cc_wwan_data_save_settings (data, NULL, NULL, NULL);
+
+ /* Set high priority for currently selected APN */
+ data = cc_wwan_device_get_data (row->device);
+ cc_wwan_data_set_priority (data, CC_WWAN_APN_PRIORITY_HIGH);
+ cc_wwan_data_save_settings (data, NULL, NULL, NULL);
+
+ self->data_device = row->device;
+ gtk_container_foreach (GTK_CONTAINER (self->data_select_listbox),
+ (GtkCallback) cc_wwan_panel_update_data_selection, self);
+}
+
+static void
+wwan_on_airplane_off_clicked_cb (CcWwanPanel *self)
+{
+ g_debug ("Airplane Mode Off clicked, disabling airplane mode");
+ g_dbus_proxy_call (self->rfkill_proxy,
+ "org.freedesktop.DBus.Properties.Set",
+ g_variant_new_parsed ("('org.gnome.SettingsDaemon.Rfkill',"
+ "'AirplaneMode', %v)",
+ g_variant_new_boolean (FALSE)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ self->cancellable,
+ NULL,
+ NULL);
+}
+
+static gboolean
+cc_wwan_panel_get_cached_dbus_property (GDBusProxy *proxy,
+ const gchar *property)
+{
+ g_autoptr(GVariant) result = NULL;
+
+ g_assert (G_IS_DBUS_PROXY (proxy));
+ g_assert (property && *property);
+
+ result = g_dbus_proxy_get_cached_property (proxy, property);
+ g_assert (!result || g_variant_is_of_type (result, G_VARIANT_TYPE_BOOLEAN));
+
+ return result ? g_variant_get_boolean (result) : FALSE;
+}
+
+static void
+cc_wwan_panel_update_view (CcWwanPanel *self)
+{
+ gboolean has_airplane, is_airplane = FALSE, enabled = FALSE;
+
+ has_airplane = cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "HasAirplaneMode");
+ has_airplane &= cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "ShouldShowAirplaneMode");
+
+ if (has_airplane)
+ {
+ is_airplane = cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "AirplaneMode");
+ is_airplane |= cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "HardwareAirplaneMode");
+ }
+
+ if (self->nm_client)
+ enabled = nm_client_wwan_get_enabled (self->nm_client);
+
+ if (has_airplane && is_airplane)
+ gtk_stack_set_visible_child_name (self->main_stack, "airplane-mode");
+ else if (enabled && g_list_model_get_n_items (G_LIST_MODEL (self->devices)) > 0)
+ gtk_stack_set_visible_child_name (self->main_stack, "device-settings");
+ else
+ gtk_stack_set_visible_child_name (self->main_stack, "no-wwan-devices");
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->enable_switch), !is_airplane);
+
+ if (enabled)
+ gtk_revealer_set_reveal_child (self->multi_device_revealer,
+ g_list_model_get_n_items (G_LIST_MODEL (self->devices)) > 1);
+}
+
+static void
+cc_wwan_panel_on_notification_closed (CcWwanPanel *self,
+ GtkWidget *button)
+{
+ gtk_revealer_set_reveal_child (self->notification_revealer, FALSE);
+
+ if (self->revealer_timeout_id != 0)
+ g_source_remove (self->revealer_timeout_id);
+
+ self->revealer_timeout_id = 0;
+}
+
+static gboolean
+cc_wwan_panel_on_notification_timeout (gpointer user_data)
+{
+ cc_wwan_panel_on_notification_closed (user_data, NULL);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+cc_wwan_panel_notification_changed_cb (CcWwanPanel *self)
+{
+ const gchar *label;
+
+ label = gtk_label_get_label (self->notification_label);
+
+ if (label && *label)
+ {
+ gtk_revealer_set_reveal_child (self->notification_revealer, TRUE);
+ self->revealer_timeout_id = g_timeout_add_seconds (5, cc_wwan_panel_on_notification_timeout, self);
+ }
+ else
+ {
+ cc_wwan_panel_on_notification_closed (self, NULL);
+ }
+}
+
+static void
+cc_wwan_panel_add_device (CcWwanPanel *self,
+ CcWwanDevice *device)
+{
+ CcWwanDevicePage *device_page;
+ g_autofree gchar *operator_name = NULL;
+ g_autofree gchar *stack_name = NULL;
+ guint n_items;
+
+ g_list_store_append (self->devices, device);
+
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->devices));
+ operator_name = g_strdup_printf (_("SIM %d"), n_items);
+ stack_name = g_strdup_printf ("sim-%d", n_items);
+
+ device_page = cc_wwan_device_page_new (device, GTK_WIDGET (self->notification_label));
+ cc_wwan_device_page_set_sim_index (device_page, n_items);
+ gtk_stack_add_titled (self->devices_stack,
+ GTK_WIDGET (device_page), stack_name, operator_name);
+}
+
+static void
+cc_wwan_panel_update_page_title (CcWwanDevicePage *device_page,
+ CcWwanPanel *self)
+{
+ g_autofree gchar *title = NULL;
+ g_autofree gchar *name = NULL;
+ CcWwanDevice *device;
+ GtkWidget *parent;
+ gint index;
+
+ device = cc_wwan_device_page_get_device (device_page);
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (device_page));
+ index = wwan_model_get_item_index (G_LIST_MODEL (self->devices), device);
+
+ if (index == -1)
+ g_return_if_reached ();
+
+ /* index starts with 0, but we need human readable index to be 1+ */
+ cc_wwan_device_page_set_sim_index (device_page, index + 1);
+ title = g_strdup_printf (_("SIM %d"), index + 1);
+ name = g_strdup_printf ("sim-%d", index + 1);
+ gtk_container_child_set (GTK_CONTAINER (parent),
+ GTK_WIDGET (device_page),
+ "title", title,
+ "name", name,
+ NULL);
+}
+
+static void
+cc_wwan_panel_remove_mm_object (CcWwanPanel *self,
+ MMObject *mm_object)
+{
+ g_autoptr(CcWwanDevice) device = NULL;
+ GtkWidget *device_page;
+ g_autofree gchar *stack_name = NULL;
+ guint n_items;
+ gint index;
+
+ device = wwan_model_get_item_from_mm_object (G_LIST_MODEL (self->devices), mm_object);
+
+ if (!device)
+ return;
+
+ index = wwan_model_get_item_index (G_LIST_MODEL (self->data_devices), device);
+ if (index != -1)
+ g_list_store_remove (self->data_devices, index);
+
+ index = wwan_model_get_item_index (G_LIST_MODEL (self->devices), device);
+ if (index == -1)
+ return;
+
+ g_list_store_remove (self->devices, index);
+ stack_name = g_strdup_printf ("sim-%d", index + 1);
+ device_page = gtk_stack_get_child_by_name (self->devices_stack, stack_name);
+ gtk_container_remove (GTK_CONTAINER (self->devices_stack), device_page);
+
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->data_devices));
+ g_list_model_items_changed (G_LIST_MODEL (self->data_devices), 0, n_items, n_items);
+ gtk_container_foreach (GTK_CONTAINER (self->devices_stack),
+ (GtkCallback)cc_wwan_panel_update_page_title,
+ self);
+}
+
+static void
+cc_wwan_panel_update_data_connections (CcWwanPanel *self)
+{
+ CcWwanData *device_data, *active_data = NULL;
+ guint n_items;
+ gint i;
+
+ /*
+ * We can’t predict the order in which the data of device is enabled.
+ * But we have to keep data store in the same order as device store.
+ * So let’s remove every data device and re-add.
+ */
+ g_list_store_remove_all (self->data_devices);
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->devices));
+
+ for (i = 0; i < n_items; i++)
+ {
+ g_autoptr(CcWwanDevice) device = NULL;
+
+ device = g_list_model_get_item (G_LIST_MODEL (self->devices), i);
+ device_data = cc_wwan_device_get_data (device);
+
+ if (!device_data)
+ continue;
+
+ if ((!active_data ||
+ cc_wwan_data_get_priority (device_data) > cc_wwan_data_get_priority (active_data)) &&
+ cc_wwan_data_get_enabled (device_data))
+ {
+ active_data = device_data;
+ self->data_device = device;
+ }
+
+ if (cc_wwan_data_get_enabled (device_data))
+ g_list_store_append (self->data_devices, device);
+ }
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->data_sim_select_listbox),
+ g_list_model_get_n_items (G_LIST_MODEL (self->data_devices)) > 1);
+ if (active_data)
+ gtk_container_foreach (GTK_CONTAINER (self->data_select_listbox),
+ (GtkCallback)cc_wwan_panel_update_data_selection, self);
+ else
+ gtk_label_set_label (self->data_sim_label, "");
+}
+
+static void
+cc_wwan_panel_update_devices (CcWwanPanel *self)
+{
+ GList *devices, *iter;
+
+ devices = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->mm_manager));
+
+ for (iter = devices; iter; iter = iter->next)
+ {
+ MMObject *mm_object = iter->data;
+ CcWwanDevice *device;
+
+ if(!wwan_panel_device_is_supported (iter->data))
+ continue;
+
+ device = cc_wwan_device_new (mm_object, G_OBJECT (self->nm_client));
+ cc_wwan_panel_add_device (self, device);
+ g_signal_connect_object (device, "notify::has-data",
+ G_CALLBACK (cc_wwan_panel_update_data_connections),
+ self, G_CONNECT_SWAPPED);
+
+ if (cc_wwan_device_get_data (device))
+ g_list_store_append (self->data_devices, device);
+ }
+
+ cc_wwan_panel_update_data_connections (self);
+ handle_argv (self);
+}
+
+static void
+wwan_panel_device_added_cb (CcWwanPanel *self,
+ GDBusObject *object)
+{
+ CcWwanDevice *device;
+
+ if(!wwan_panel_device_is_supported (object))
+ return;
+
+ device = cc_wwan_device_new (MM_OBJECT (object), G_OBJECT (self->nm_client));
+ cc_wwan_panel_add_device (self, device);
+ g_signal_connect_object (device, "notify::has-data",
+ G_CALLBACK (cc_wwan_panel_update_data_connections),
+ self, G_CONNECT_SWAPPED);
+ cc_wwan_panel_update_view (self);
+ handle_argv (self);
+}
+
+static void
+wwan_panel_device_removed_cb (CcWwanPanel *self,
+ GDBusObject *object)
+{
+ if (!wwan_panel_device_is_supported (object))
+ return;
+
+ cc_wwan_panel_remove_mm_object (self, MM_OBJECT (object));
+
+ gtk_revealer_set_reveal_child (self->multi_device_revealer,
+ g_list_model_get_n_items (G_LIST_MODEL (self->devices)) > 1);
+}
+
+static GPtrArray *
+variant_av_to_string_array (GVariant *array)
+{
+ GVariant *v;
+ GPtrArray *strv;
+ GVariantIter iter;
+ gsize count;
+
+ count = g_variant_iter_init (&iter, array);
+ strv = g_ptr_array_sized_new (count + 1);
+
+ while (g_variant_iter_next (&iter, "v", &v))
+ {
+ g_ptr_array_add (strv, (gpointer)g_variant_get_string (v, NULL));
+ g_variant_unref (v);
+ }
+ g_ptr_array_add (strv, NULL); /* NULL-terminate the strv data array */
+
+ return strv;
+}
+
+static void
+cc_wwan_panel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWwanPanel *self = CC_WWAN_PANEL (object);
+
+ switch (property_id)
+ {
+ case PROP_PARAMETERS:
+ {
+ GVariant *parameters;
+
+ reset_command_line_args (self);
+
+ parameters = g_value_get_variant (value);
+ if (parameters)
+ {
+ g_autoptr(GPtrArray) array = NULL;
+ const gchar **args;
+
+ array = variant_av_to_string_array (parameters);
+ args = (const gchar **) array->pdata;
+
+ g_debug ("Invoked with operation %s", args[0]);
+
+ if (args[0])
+ self->arg_operation = cmdline_operation_from_string (args[0]);
+ if (args[0] && args[1])
+ self->arg_device = g_strdup (args[1]);
+
+ if (!verify_argv (self, (const char **) args))
+ {
+ reset_command_line_args (self);
+ return;
+ }
+ g_debug ("Calling handle_argv() after setting property");
+ handle_argv (self);
+ }
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+cc_wwan_panel_constructed (GObject *object)
+{
+ CcWwanPanel *self = (CcWwanPanel *)object;
+
+ G_OBJECT_CLASS (cc_wwan_panel_parent_class)->constructed (object);
+
+ cc_shell_embed_widget_in_header (cc_panel_get_shell (CC_PANEL (self)),
+ GTK_WIDGET (self->enable_switch), GTK_POS_RIGHT);
+
+ if (self->nm_client)
+ {
+ g_object_bind_property (self->nm_client, "wwan-enabled",
+ self->enable_switch, "active",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+ }
+}
+
+static void
+cc_wwan_panel_dispose (GObject *object)
+{
+ CcWwanPanel *self = (CcWwanPanel *)object;
+
+ if (self->revealer_timeout_id != 0)
+ g_source_remove (self->revealer_timeout_id);
+
+ self->revealer_timeout_id = 0;
+
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_object (&self->devices);
+ g_clear_object (&self->data_devices);
+ g_clear_object (&self->mm_manager);
+ g_clear_object (&self->nm_client);
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->rfkill_proxy);
+ g_clear_pointer (&self->arg_device, g_free);
+
+ G_OBJECT_CLASS (cc_wwan_panel_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_panel_class_init (CcWwanPanelClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = cc_wwan_panel_set_property;
+ object_class->constructed = cc_wwan_panel_constructed;
+ object_class->dispose = cc_wwan_panel_dispose;
+
+ g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters");
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/wwan/cc-wwan-panel.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_select_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_select_popover);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_sim_label);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_sim_select_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, devices_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, devices_switcher);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, enable_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, main_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, multi_device_revealer);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, notification_label);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, notification_revealer);
+
+ gtk_widget_class_bind_template_callback (widget_class, wwan_on_airplane_off_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, wwan_notification_close_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, wwan_data_selector_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_data_item_activate_cb);
+}
+
+static void
+cc_wwan_panel_init (CcWwanPanel *self)
+{
+ g_autoptr(GError) error = NULL;
+
+ g_resources_register (cc_wwan_get_resource ());
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->cancellable = g_cancellable_new ();
+ self->devices = g_list_store_new (CC_TYPE_WWAN_DEVICE);
+ self->data_devices = g_list_store_new (CC_TYPE_WWAN_DEVICE);
+ gtk_list_box_bind_model (GTK_LIST_BOX (self->data_select_listbox),
+ G_LIST_MODEL (self->data_devices),
+ (GtkListBoxCreateWidgetFunc) cc_data_device_row_new,
+ self, NULL);
+
+ g_signal_connect_object (self->notification_label, "notify::label",
+ G_CALLBACK (cc_wwan_panel_notification_changed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ if (cc_object_storage_has_object (CC_OBJECT_NMCLIENT))
+ {
+ self->nm_client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT);
+ g_signal_connect_object (self->nm_client,
+ "notify::wwan-enabled",
+ G_CALLBACK (cc_wwan_panel_update_view),
+ self, G_CONNECT_SWAPPED);
+
+ }
+ else
+ {
+ g_warn_if_reached ();
+ }
+
+ if (cc_object_storage_has_object ("CcObjectStorage::mm-manager"))
+ {
+ self->mm_manager = cc_object_storage_get_object ("CcObjectStorage::mm-manager");
+
+ g_signal_connect_object (self->mm_manager, "object-added",
+ G_CALLBACK (wwan_panel_device_added_cb),
+ self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->mm_manager, "object-removed",
+ G_CALLBACK (wwan_panel_device_removed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ cc_wwan_panel_update_devices (self);
+ }
+ else
+ {
+ g_warn_if_reached ();
+ }
+
+ /* Acquire Airplane Mode proxy */
+ self->rfkill_proxy = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "org.gnome.SettingsDaemon.Rfkill",
+ "/org/gnome/SettingsDaemon/Rfkill",
+ "org.gnome.SettingsDaemon.Rfkill",
+ self->cancellable,
+ &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_printerr ("Error creating rfkill proxy: %s\n", error->message);
+ }
+ else
+ {
+ g_signal_connect_object (self->rfkill_proxy,
+ "g-properties-changed",
+ G_CALLBACK (cc_wwan_panel_update_view),
+ self, G_CONNECT_SWAPPED);
+
+ cc_wwan_panel_update_view (self);
+ }
+}
+
+static void
+wwan_update_panel_visibility (MMManager *mm_manager)
+{
+ CcApplication *application;
+ GList *devices;
+ gboolean has_wwan;
+
+ g_assert (MM_IS_MANAGER (mm_manager));
+
+ CC_TRACE_MSG ("Updating WWAN panel visibility");
+
+ has_wwan = FALSE;
+ devices = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (mm_manager));
+
+ for (GList *item = devices; item != NULL; item = item->next)
+ {
+ if(wwan_panel_device_is_supported (item->data))
+ {
+ has_wwan = TRUE;
+ break;
+ }
+ }
+
+ /* Set the new visibility */
+ application = CC_APPLICATION (g_application_get_default ());
+ cc_shell_model_set_panel_visibility (cc_application_get_model (application),
+ "wwan",
+ has_wwan ? CC_PANEL_VISIBLE : CC_PANEL_VISIBLE_IN_SEARCH);
+
+ g_debug ("WWAN panel visible: %s", has_wwan ? "yes" : "no");
+
+ g_list_free_full (devices, (GDestroyNotify)g_object_unref);
+}
+
+void
+cc_wwan_panel_static_init_func (void)
+{
+ g_autoptr(GDBusConnection) system_bus = NULL;
+ g_autoptr(MMManager) mm_manager = NULL;
+ g_autoptr(GError) error = NULL;
+
+ /*
+ * There could be other modems that are only handled by rfkill,
+ * and not available via ModemManager. But as this panel
+ * makes use of ModemManager APIs, we only care devices
+ * supported by ModemManager.
+ */
+ system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (system_bus == NULL)
+ g_warning ("Error connecting to system D-Bus: %s", error->message);
+ else
+ mm_manager = mm_manager_new_sync (system_bus,
+ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
+ NULL, &error);
+
+ if (mm_manager == NULL)
+ {
+ CcApplication *application;
+
+ g_warning ("Error connecting to ModemManager: %s", error->message);
+
+ application = CC_APPLICATION (g_application_get_default ());
+ cc_shell_model_set_panel_visibility (cc_application_get_model (application),
+ "wwan", FALSE);
+ return;
+ }
+ else
+ {
+ cc_object_storage_add_object ("CcObjectStorage::mm-manager", mm_manager);
+ }
+
+ g_debug ("Monitoring ModemManager for WWAN devices");
+
+ g_signal_connect (mm_manager, "object-added", G_CALLBACK (wwan_update_panel_visibility), NULL);
+ g_signal_connect (mm_manager, "object-removed", G_CALLBACK (wwan_update_panel_visibility), NULL);
+
+ wwan_update_panel_visibility (mm_manager);
+}
diff --git a/panels/wwan/cc-wwan-panel.h b/panels/wwan/cc-wwan-panel.h
new file mode 100644
index 000000000..57d2dae26
--- /dev/null
+++ b/panels/wwan/cc-wwan-panel.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-panel.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <shell/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_PANEL (cc_wwan_panel_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanPanel, cc_wwan_panel, CC, WWAN_PANEL, CcPanel)
+
+void cc_wwan_panel_static_init_func (void);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-panel.ui b/panels/wwan/cc-wwan-panel.ui
new file mode 100644
index 000000000..5258c42c3
--- /dev/null
+++ b/panels/wwan/cc-wwan-panel.ui
@@ -0,0 +1,336 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcWwanPanel" parent="CcPanel">
+ <property name="visible">1</property>
+
+ <child>
+ <object class="GtkOverlay">
+ <property name="visible">1</property>
+
+ <!-- Notification Revealer -->
+ <child type="overlay">
+ <object class="GtkRevealer" id="notification_revealer">
+ <property name="visible">1</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="spacing">12</property>
+ <style>
+ <class name="frame" />
+ <class name="app-notification" />
+ </style>
+ <child>
+ <object class="GtkLabel" id="notification_label">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="wrap-mode">word</property>
+ <property name="use-markup">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <signal name="clicked" handler="wwan_notification_close_clicked_cb" swapped="yes" />
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Close</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">window-close-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">1</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="min-content-height">500</property>
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">1</property>
+ <property name="margin-top">0</property>
+ <property name="margin-bottom">32</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkStack" id="main_stack">
+ <property name="visible">1</property>
+ <property name="homogeneous">0</property>
+ <property name="transition-type">crossfade</property>
+
+ <!-- "No WWAN Adapter" page -->
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="expand">1</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">network-cellular-offline-symbolic</property>
+ <property name="pixel-size">192</property>
+ <property name="margin-bottom">18</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="label" translatable="yes">No WWAN Adapter Found</property>
+ <attributes>
+ <attribute name="weight" value="bold" />
+ <attribute name="scale" value="1.2" />
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="label" translatable="yes">Make sure you have a Wireless Wan/Cellular device</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">no-wwan-devices</property>
+ </packing>
+ </child>
+
+ <!-- "Airplane Mode" page -->
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="expand">1</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">airplane-mode-symbolic</property>
+ <property name="pixel-size">192</property>
+ <property name="margin-bottom">18</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="label" translatable="yes">Airplane Mode On</property>
+ <attributes>
+ <attribute name="weight" value="bold" />
+ <attribute name="scale" value="1.2" />
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="label" translatable="yes">Wireless Wan is disabled when airplane mode is on</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">1</property>
+ <property name="halign">center</property>
+ <property name="use-underline">1</property>
+ <property name="margin-top">24</property>
+ <property name="label" translatable="yes">_Turn off Airplane Mode</property>
+ <signal name="clicked" handler="wwan_on_airplane_off_clicked_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">airplane-mode</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkRevealer" id="multi_device_revealer">
+ <property name="visible">1</property>
+ <property name="margin-top">18</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+
+ <!-- Data SIM selector -->
+ <child>
+ <object class="GtkListBox" id="data_sim_select_listbox">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <property name="margin-bottom">32</property>
+ <signal name="row-activated" handler="wwan_data_selector_clicked_cb" swapped="yes" />
+ <style>
+ <class name="frame" />
+ </style>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">1</property>
+ <property name="border-width">9</property>
+ <property name="margin-start">9</property>
+ <property name="margin-end">9</property>
+ <property name="column-spacing">12</property>
+
+ <!-- Title -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="label" translatable="yes">Data Connection</property>
+ <property name="xalign">0.0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <!-- SubTitle -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="label" translatable="yes">SIM card used for internet</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="scale" value="0.88" />
+ </attributes>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+
+ <!-- Network Name -->
+ <child>
+ <object class="GtkLabel" id="data_sim_label">
+ <property name="visible">1</property>
+ <property name="valign">center</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+
+ <!-- Popover Arrow -->
+ <child>
+ <object class="GtkImage" id="popover_arrow">
+ <property name="visible">1</property>
+ <property name="valign">center</property>
+ <property name="icon-name">pan-down-symbolic</property>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Device (SIM) Name -->
+ <child>
+ <object class="GtkStackSwitcher" id="devices_switcher">
+ <property name="stack">devices_stack</property>
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">center</property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Device (SIM) settings page -->
+ <child>
+ <object class="GtkStack" id="devices_stack">
+ <property name="visible">1</property>
+ <property name="homogeneous">0</property>
+ </object>
+ </child>
+
+ </object>
+ <packing>
+ <property name="name">device-settings</property>
+ </packing>
+ </child>
+
+ </object> <!-- ./GtkStack main_stack -->
+ </child>
+ </object>
+ </child>
+
+ </object> <!-- ./HdyClamp -->
+ </child>
+ </object> <!-- ./GtkScrolledWindow -->
+ </child>
+
+ </object>
+ </child>
+
+ </template>
+
+ <object class="GtkPopover" id="data_select_popover">
+ <property name="position">bottom</property>
+ <property name="relative-to">popover_arrow</property>
+ <child>
+ <object class="GtkListBox" id="data_select_listbox">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="cc_wwan_data_item_activate_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+
+ <!-- Cellular panel on/off switch -->
+ <object class="GtkSwitch" id="enable_switch">
+ <property name="visible">1</property>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Enable Mobile Network</property>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.c b/panels/wwan/cc-wwan-sim-lock-dialog.c
new file mode 100644
index 000000000..14adbf415
--- /dev/null
+++ b/panels/wwan/cc-wwan-sim-lock-dialog.c
@@ -0,0 +1,310 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-network-dialog.c
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wwan-sim-lock-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "list-box-helper.h"
+#include "cc-list-row.h"
+#include "cc-wwan-sim-lock-dialog.h"
+#include "cc-wwan-resources.h"
+
+/**
+ * @short_description: Dialog to manage SIM Locks like PIN
+ */
+
+#define PIN_MINIMUM_LENGTH 4
+#define PIN_MAXIMUM_LENGTH 8
+
+struct _CcWwanSimLockDialog
+{
+ GtkDialog parent_instance;
+
+ CcWwanDevice *device;
+
+ GtkButton *apply_button;
+ GtkStack *button_stack;
+ GtkGrid *lock_change_grid;
+ CcListRow *lock_row;
+ GtkEntry *new_pin_entry;
+ GtkButton *next_button;
+ GtkEntry *pin_confirm_entry;
+ GtkEntry *pin_entry;
+ GtkStack *pin_settings_stack;
+};
+
+G_DEFINE_TYPE (CcWwanSimLockDialog, cc_wwan_sim_lock_dialog, GTK_TYPE_DIALOG)
+
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+cc_wwan_sim_lock_changed_cb (CcWwanSimLockDialog *self)
+{
+ gboolean row_enabled, lock_enabled;
+
+ lock_enabled = cc_wwan_device_get_sim_lock (self->device);
+ row_enabled = cc_list_row_get_active (self->lock_row);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), lock_enabled != row_enabled);
+ gtk_widget_set_visible (GTK_WIDGET (self->lock_change_grid), row_enabled && lock_enabled);
+}
+
+static void
+cc_wwan_pin_next_clicked_cb (CcWwanSimLockDialog *self)
+{
+ gtk_stack_set_visible_child_name (self->pin_settings_stack, "pin-entry");
+ gtk_entry_set_text (self->pin_entry, "");
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE);
+ gtk_stack_set_visible_child (self->button_stack,
+ GTK_WIDGET (self->apply_button));
+}
+
+static void
+cc_wwan_pin_apply_clicked_cb (CcWwanSimLockDialog *self)
+{
+ const gchar *pin, *new_pin;
+ gboolean row_enabled, lock_enabled;
+
+ gtk_widget_hide (GTK_WIDGET (self));
+
+ lock_enabled = cc_wwan_device_get_sim_lock (self->device);
+ row_enabled = cc_list_row_get_active (self->lock_row);
+ pin = gtk_entry_get_text (self->pin_entry);
+ new_pin = gtk_entry_get_text (self->new_pin_entry);
+
+ if (lock_enabled != row_enabled)
+ {
+ if (row_enabled)
+ cc_wwan_device_enable_pin (self->device, pin, NULL, NULL, NULL);
+ else
+ cc_wwan_device_disable_pin (self->device, pin, NULL, NULL, NULL);
+
+ return;
+ }
+
+ cc_wwan_device_change_pin (self->device, pin, new_pin, NULL, NULL, NULL);
+}
+
+static void
+cc_wwan_pin_entry_text_inserted_cb (CcWwanSimLockDialog *self,
+ gchar *new_text,
+ gint new_text_length,
+ gpointer position,
+ GtkEditable *editable)
+{
+ size_t digit_end;
+ size_t len;
+
+ if (!new_text || !*new_text)
+ return;
+
+ if (new_text_length == 1 && g_ascii_isdigit (*new_text))
+ return;
+
+ if (new_text_length == -1)
+ len = strlen (new_text);
+ else
+ len = new_text_length;
+
+ if (len == 1 && g_ascii_isdigit (*new_text))
+ return;
+
+ digit_end = strspn (new_text, "1234567890");
+
+ /* The maximum length possible for PIN is 8 */
+ if (len <= 8 && digit_end == len)
+ return;
+
+ g_signal_stop_emission_by_name (editable, "insert-text");
+ gtk_widget_error_bell (GTK_WIDGET (editable));
+}
+
+static void
+cc_wwan_pin_entry_changed_cb (CcWwanSimLockDialog *self)
+{
+ const gchar *new_pin, *confirm_pin;
+
+ new_pin = gtk_entry_get_text (self->new_pin_entry);
+ confirm_pin = gtk_entry_get_text (self->pin_confirm_entry);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), FALSE);
+
+ /* A PIN should have a minimum length of 4 */
+ if (!new_pin || !confirm_pin || strlen (new_pin) < 4)
+ return;
+
+ if (g_str_equal (new_pin, confirm_pin))
+ gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), TRUE);
+}
+
+
+static void
+cc_wwan_pin_entered_cb (CcWwanSimLockDialog *self)
+{
+ const gchar *pin;
+ gsize len;
+ gboolean enable_apply;
+
+ pin = gtk_entry_get_text (self->pin_entry);
+
+ if (!pin || !*pin)
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE);
+ return;
+ }
+
+ len = strlen (pin);
+ enable_apply = len >= PIN_MINIMUM_LENGTH && len <= PIN_MAXIMUM_LENGTH;
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), enable_apply);
+}
+
+static void
+cc_wwan_sim_lock_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWwanSimLockDialog *self = (CcWwanSimLockDialog *)object;
+
+ switch (prop_id)
+ {
+ case PROP_DEVICE:
+ self->device = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wwan_sim_lock_dialog_show (GtkWidget *widget)
+{
+ CcWwanSimLockDialog *self = (CcWwanSimLockDialog *)widget;
+ gboolean lock_enabled;
+
+ gtk_entry_set_text (self->pin_entry, "");
+ gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE);
+
+ lock_enabled = cc_wwan_device_get_sim_lock (self->device);
+ g_object_set (self->lock_row, "active", lock_enabled, NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->lock_change_grid), lock_enabled);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), FALSE);
+ gtk_stack_set_visible_child (self->button_stack,
+ GTK_WIDGET (self->next_button));
+ gtk_button_set_label (self->apply_button, _("_Set"));
+
+ gtk_stack_set_visible_child_name (self->pin_settings_stack, "pin-settings");
+
+ gtk_entry_set_text (self->pin_entry, "");
+ gtk_entry_set_text (self->new_pin_entry, "");
+ gtk_entry_set_text (self->pin_confirm_entry, "");
+
+ GTK_WIDGET_CLASS (cc_wwan_sim_lock_dialog_parent_class)->show (widget);
+}
+
+static void
+cc_wwan_sim_lock_dialog_dispose (GObject *object)
+{
+ CcWwanSimLockDialog *self = (CcWwanSimLockDialog *)object;
+
+ g_clear_object (&self->device);
+
+ G_OBJECT_CLASS (cc_wwan_sim_lock_dialog_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_sim_lock_dialog_class_init (CcWwanSimLockDialogClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = cc_wwan_sim_lock_dialog_set_property;
+ object_class->dispose = cc_wwan_sim_lock_dialog_dispose;
+
+ widget_class->show = cc_wwan_sim_lock_dialog_show;
+
+ properties[PROP_DEVICE] =
+ g_param_spec_object ("device",
+ "Device",
+ "The WWAN Device",
+ CC_TYPE_WWAN_DEVICE,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/wwan/cc-wwan-sim-lock-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, apply_button);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, button_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, lock_change_grid);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, lock_row);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, new_pin_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, next_button);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, pin_confirm_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, pin_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, pin_settings_stack);
+
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_sim_lock_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_next_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_apply_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_entry_text_inserted_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_entry_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_entered_cb);
+}
+
+static void
+cc_wwan_sim_lock_dialog_init (CcWwanSimLockDialog *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcWwanSimLockDialog *
+cc_wwan_sim_lock_dialog_new (GtkWindow *parent_window,
+ CcWwanDevice *device)
+{
+ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+ return g_object_new (CC_TYPE_WWAN_SIM_LOCK_DIALOG,
+ "transient-for", parent_window,
+ "use-header-bar", 1,
+ "device", device,
+ NULL);
+}
diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.h b/panels/wwan/cc-wwan-sim-lock-dialog.h
new file mode 100644
index 000000000..b6d1d5a9e
--- /dev/null
+++ b/panels/wwan/cc-wwan-sim-lock-dialog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-sim-lock-dialog.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <handy.h>
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_SIM_LOCK_DIALOG (cc_wwan_sim_lock_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanSimLockDialog, cc_wwan_sim_lock_dialog, CC, WWAN_SIM_LOCK_DIALOG, GtkDialog)
+
+CcWwanSimLockDialog *cc_wwan_sim_lock_dialog_new (GtkWindow *parent_window,
+ CcWwanDevice *device);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.ui b/panels/wwan/cc-wwan-sim-lock-dialog.ui
new file mode 100644
index 000000000..48a946be4
--- /dev/null
+++ b/panels/wwan/cc-wwan-sim-lock-dialog.ui
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcWwanSimLockDialog" parent="GtkDialog">
+ <property name="default-height">480</property>
+ <property name="default-width">360</property>
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+ <child type="titlebar">
+ <object class="GtkHeaderBar">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes">SIM Lock</property>
+ <child>
+ <object class="GtkStack" id="button_stack">
+ <property name="visible">1</property>
+
+ <!-- Next Buttoon -->
+ <child>
+ <object class="GtkButton" id="next_button">
+ <property name="visible">1</property>
+ <property name="sensitive">0</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Next</property>
+ <signal name="clicked" handler="cc_wwan_pin_next_clicked_cb" swapped="yes" />
+ <style>
+ <class name="suggested-action" />
+ </style>
+ </object>
+ <packing>
+ <property name="name">next</property>
+ </packing>
+ </child>
+
+ <!-- Apply button -->
+ <child>
+ <object class="GtkButton" id="apply_button">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <signal name="clicked" handler="cc_wwan_pin_apply_clicked_cb" swapped="yes" />
+ <style>
+ <class name="suggested-action" />
+ </style>
+ </object>
+ <packing>
+ <property name="name">apply</property>
+ </packing>
+ </child>
+
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="border-width">0</property>
+ <property name="width-request">340</property>
+ <property name="height-request">360</property>
+
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">1</property>
+ <property name="margin-top">32</property>
+ <property name="margin-bottom">32</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <child>
+ <object class="GtkOverlay">
+ <property name="visible">1</property>
+ <child type="overlay">
+ <object class="GtkRevealer" id="notification_revealer">
+ <property name="visible">1</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="spacing">12</property>
+ <style>
+ <class name="frame" />
+ <class name="app-notification" />
+ </style>
+ <child>
+ <object class="GtkLabel" id="notification_label">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="use-markup">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <!-- <signal name="clicked" handler="cc_wwan_on_notification_closed" object="CcWwanSimLockDialog" swapped="yes" /> -->
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Close</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">window-close-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkStack" id="pin_settings_stack">
+ <property name="visible">1</property>
+ <property name="transition-type">slide-left</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+
+ <!-- SIM Lock Switch -->
+ <child>
+ <object class="GtkListBox">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <property name="margin-bottom">18</property>
+ <style>
+ <class name="frame" />
+ </style>
+ <child>
+ <object class="CcListRow" id="lock_row">
+ <property name="visible">1</property>
+ <property name="show-switch">1</property>
+ <property name="use-underline">1</property>
+ <property name="title" translatable="yes">_Lock SIM with PIN</property>
+ <signal name="notify::active" handler="cc_wwan_sim_lock_changed_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkGrid" id="lock_change_grid">
+ <property name="visible">0</property>
+ <property name="row-spacing">18</property>
+ <property name="column-spacing">12</property>
+
+ <!-- SIM Lock Change Title -->
+ <child>
+ <object class="GtkLabel" id="lock_change_title">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Change PIN</property>
+ <property name="margin-bottom">9</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+
+ <!-- PIN Entry -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label">New PIN</property>
+ <property name="halign">end</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="new_pin_entry">
+ <property name="visible">1</property>
+ <property name="visibility">0</property>
+ <property name="input-purpose">password</property>
+ <property name="input-hints">no-emoji</property>
+ <property name="max-length">8</property>
+ <property name="max-width-chars">32</property>
+ <signal name="notify::text" handler="cc_wwan_pin_entry_changed_cb" swapped="yes" />
+ <signal name="insert-text" handler="cc_wwan_pin_entry_text_inserted_cb" swapped="yes" />
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+
+ <!-- Confirm PIN Entry -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label">Confirm</property>
+ <property name="halign">end</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="pin_confirm_entry">
+ <property name="visible">1</property>
+ <property name="visibility">0</property>
+ <property name="input-purpose">password</property>
+ <property name="input-hints">no-emoji</property>
+ <property name="max-length">8</property>
+ <property name="max-width-chars">32</property>
+ <signal name="notify::text" handler="cc_wwan_pin_entry_changed_cb" swapped="yes" />
+ <signal name="insert-text" handler="cc_wwan_pin_entry_changed_cb" swapped="yes" />
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+
+ </object>
+ <packing>
+ <property name="name">pin-settings</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="expand">1</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="pixel-size">128</property>
+ <property name="icon-name">dialog-password-symbolic</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Enter current PIN to change SIM lock settings</property>
+ <property name="margin-bottom">24</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="pin_entry">
+ <property name="visible">1</property>
+ <property name="visibility">0</property>
+ <property name="input-purpose">password</property>
+ <property name="input-hints">no-emoji</property>
+ <property name="max-length">8</property>
+ <property name="max-width-chars">32</property>
+ <signal name="notify::text" handler="cc_wwan_pin_entered_cb" swapped="yes" />
+ <signal name="insert-text" handler="cc_wwan_pin_entry_text_inserted_cb" swapped="yes" />
+ <!-- We have custom widgets and no actions, so "activates-default" won't work -->
+ <signal name="activate" handler="cc_wwan_pin_apply_clicked_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">pin-entry</property>
+ </packing>
+ </child>
+
+ </object> <!-- ./GtkStack pin_settings_stack -->
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child> <!-- ./internal-child -->
+
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Cancel</property>
+ <signal name="clicked" handler="gtk_widget_hide" swapped="yes"/>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+
+ </template>
+</interface>
diff --git a/panels/wwan/gnome-wwan-panel.desktop.in.in b/panels/wwan/gnome-wwan-panel.desktop.in.in
new file mode 100644
index 000000000..351a8edde
--- /dev/null
+++ b/panels/wwan/gnome-wwan-panel.desktop.in.in
@@ -0,0 +1,16 @@
+[Desktop Entry]
+Name=Mobile Network
+Comment=Configure Telephony and mobile data connections
+Exec=gnome-control-center wwan
+# FIXME
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=network-cellular-signal-excellent
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;X-GNOME-NetworkSettings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings;
+OnlyShowIn=GNOME;Unity;
+StartupNotify=true
+# Translators: Search terms to find the WWAN panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=cellular;wwan;telephony;sim;mobile;
diff --git a/panels/wwan/meson.build b/panels/wwan/meson.build
new file mode 100644
index 000000000..8c1b02f26
--- /dev/null
+++ b/panels/wwan/meson.build
@@ -0,0 +1,61 @@
+gcr_dep = [dependency('gcr-3')]
+
+deps = common_deps + network_manager_deps + gcr_dep + [polkit_gobject_dep]
+panels_list += cappletname
+desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
+
+desktop_in = configure_file(
+ input : desktop + '.in.in',
+ output : desktop + '.in',
+ configuration : desktop_conf
+)
+
+i18n.merge_file(
+ desktop,
+ type : 'desktop',
+ input : desktop_in,
+ output : desktop,
+ po_dir : po_dir,
+ install : true,
+ install_dir : control_center_desktopdir
+)
+
+sources = files(
+ 'cc-wwan-panel.c',
+ 'cc-wwan-device.c',
+ 'cc-wwan-data.c',
+ 'cc-wwan-device-page.c',
+ 'cc-wwan-mode-dialog.c',
+ 'cc-wwan-network-dialog.c',
+ 'cc-wwan-details-dialog.c',
+ 'cc-wwan-sim-lock-dialog.c',
+ 'cc-wwan-apn-dialog.c',
+)
+
+resource_data = files(
+ 'cc-wwan-panel.ui',
+ 'cc-wwan-device-page.ui',
+ 'cc-wwan-mode-dialog.ui',
+ 'cc-wwan-network-dialog.ui',
+ 'cc-wwan-details-dialog.ui',
+ 'cc-wwan-sim-lock-dialog.ui',
+ 'cc-wwan-apn-dialog.ui',
+)
+
+sources += gnome.compile_resources(
+ 'cc-' + cappletname + '-resources',
+ cappletname + '.gresource.xml',
+ c_name : 'cc_' + cappletname,
+ dependencies : resource_data,
+ export : true
+)
+
+cflags += '-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir)
+
+panels_libs += static_library(
+ cappletname,
+ sources : sources,
+ include_directories : [ top_inc, common_inc ],
+ dependencies : deps,
+ c_args : cflags
+)
diff --git a/panels/wwan/wwan.gresource.xml b/panels/wwan/wwan.gresource.xml
new file mode 100644
index 000000000..f128a164a
--- /dev/null
+++ b/panels/wwan/wwan.gresource.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/control-center/wwan">
+ <file preprocess="xml-stripblanks">cc-wwan-panel.ui</file>
+ <file preprocess="xml-stripblanks">cc-wwan-device-page.ui</file>
+ <file preprocess="xml-stripblanks">cc-wwan-mode-dialog.ui</file>
+ <file preprocess="xml-stripblanks">cc-wwan-network-dialog.ui</file>
+ <file preprocess="xml-stripblanks">cc-wwan-details-dialog.ui</file>
+ <file preprocess="xml-stripblanks">cc-wwan-sim-lock-dialog.ui</file>
+ <file preprocess="xml-stripblanks">cc-wwan-apn-dialog.ui</file>
+ </gresource>
+</gresources>
diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c
index e23da0b87..e6659b789 100644
--- a/shell/cc-panel-list.c
+++ b/shell/cc-panel-list.c
@@ -381,6 +381,7 @@ static const gchar * const panel_order[] = {
/* Main page */
"wifi",
"network",
+ "wwan",
"mobile-broadband",
"bluetooth",
"background",
diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c
index f20384394..65b6555a1 100644
--- a/shell/cc-panel-loader.c
+++ b/shell/cc-panel-loader.c
@@ -64,6 +64,9 @@ extern GType cc_user_panel_get_type (void);
#ifdef BUILD_WACOM
extern GType cc_wacom_panel_get_type (void);
#endif /* BUILD_WACOM */
+#ifdef BUILD_WWAN
+extern GType cc_wwan_panel_get_type (void);
+#endif /* BUILD_WWAN */
extern GType cc_location_panel_get_type (void);
extern GType cc_camera_panel_get_type (void);
extern GType cc_microphone_panel_get_type (void);
@@ -79,6 +82,9 @@ extern void cc_wifi_panel_static_init_func (void);
#ifdef BUILD_WACOM
extern void cc_wacom_panel_static_init_func (void);
#endif /* BUILD_WACOM */
+#ifdef BUILD_WWAN
+extern void cc_wwan_panel_static_init_func (void);
+#endif /* BUILD_WWAN */
#define PANEL_TYPE(name, get_type, init_func) { name, get_type, init_func }
@@ -129,6 +135,9 @@ static CcPanelLoaderVtable default_panels[] =
#ifdef BUILD_WACOM
PANEL_TYPE("wacom", cc_wacom_panel_get_type, cc_wacom_panel_static_init_func),
#endif
+#ifdef BUILD_WWAN
+ PANEL_TYPE("wwan", cc_wwan_panel_get_type, cc_wwan_panel_static_init_func),
+#endif
};
/* Override for the panel vtable. When NULL, the default_panels will
--
2.32.0
From 610bf914b5c745c87b0be5c827515e82c07317f9 Mon Sep 17 00:00:00 2001
From: Mohammed Sadiq <sadiq@sadiqpk.org>
Date: Fri, 4 Oct 2019 16:02:23 +0530
Subject: [PATCH 3/7] network: Don't show modems supported by cellular panel
Cellular panel is already handling it
---
panels/network/cc-network-panel.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/panels/network/cc-network-panel.c b/panels/network/cc-network-panel.c
index 01b164ea0..bd4e55df8 100644
--- a/panels/network/cc-network-panel.c
+++ b/panels/network/cc-network-panel.c
@@ -382,6 +382,27 @@ update_bluetooth_section (CcNetworkPanel *self)
gtk_widget_set_visible (self->container_bluetooth, self->bluetooth_devices->len > 0);
}
+static gboolean
+wwan_panel_supports_modem (GDBusObject *object)
+{
+ MMObject *mm_object;
+ MMModem *modem;
+ MMModemCapability capability, supported_capabilities;
+
+ g_assert (G_IS_DBUS_OBJECT (object));
+
+ supported_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE;
+#if MM_CHECK_VERSION (1,14,0)
+ supported_capabilities |= MM_MODEM_CAPABILITY_5GNR;
+#endif
+
+ mm_object = MM_OBJECT (object);
+ modem = mm_object_get_modem (mm_object);
+ capability = mm_modem_get_current_capabilities (modem);
+
+ return capability & supported_capabilities;
+}
+
static void
panel_add_device (CcNetworkPanel *self, NMDevice *device)
{
@@ -425,6 +446,10 @@ panel_add_device (CcNetworkPanel *self, NMDevice *device)
nm_device_get_udi (device));
return;
}
+
+ /* This will be handled by cellular panel */
+ if (wwan_panel_supports_modem (modem_object))
+ return;
}
device_mobile = net_device_mobile_new (self->client, device, modem_object);
--
2.32.0
From b7a3b8b84641fdc600cc2ab3683da57023ef5e65 Mon Sep 17 00:00:00 2001
From: Kyle Rankin <kyle.rankin@puri.sm>
Date: Fri, 21 Feb 2020 01:28:13 +0000
Subject: [PATCH 4/7] Lower WWAN DNS Priority
The current DNS priority settings for WWAN were set far too low. Most
connections (including WiFi) do not set DNS priority (set to 0) and per
https://developer.gnome.org/NetworkManager/stable/nm-settings.html :
"A lower value is better (higher priority). Zero selects a globally
configured default value. If the latter is missing or zero too, it
defaults to 50 for VPNs and 100 for other connections."
By setting both the "low" and "high" settings to 15 and 20 respectively,
the WWAN DNS servers were always appearing above WiFi, even though WiFi
had routing priority. This caused latency and other problems when the
wwan connection was slow because the system would query those DNS
servers before WiFi ones. Beyond that, it would even cause WWAN to
override VPN DNS settings which isn't what we want.
This change puts the "low priority" setting above the default 100 that
connections get when they don't otherwise set a priority, and the "high
priority" slightly below 100. I did this instead of setting the values
to 0 because I noticed that NM doesn't seem to be aware it should
prioritize WiFi in that case so WWAN DNS servers were still sometimes
taking precedence.
---
panels/wwan/cc-wwan-data.c | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/panels/wwan/cc-wwan-data.c b/panels/wwan/cc-wwan-data.c
index 0be8f3403..4062d78fd 100644
--- a/panels/wwan/cc-wwan-data.c
+++ b/panels/wwan/cc-wwan-data.c
@@ -47,9 +47,20 @@
* of #CcWwanData changes when SIM is changed.
*/
-/* Priority for connections. larger the number, lower the priority */
-#define CC_WWAN_DNS_PRIORITY_LOW (20)
-#define CC_WWAN_DNS_PRIORITY_HIGH (15)
+/*
+ * Priority for connections. The larger the number, the lower the priority
+ * https://developer.gnome.org/NetworkManager/stable/nm-settings.html:
+ *
+ * A lower value is better (higher priority). Zero selects a globally
+ * configured default value. If the latter is missing or zero too, it
+ * defaults to 50 for VPNs and 100 for other connections.
+ *
+ * Since WiFi and other network connections will likely get the default
+ * setting of 100, set WWAN DNS priorities higher than the default, with
+ * room to allow multiple modems to set priority above/below each other.
+ */
+#define CC_WWAN_DNS_PRIORITY_LOW (120)
+#define CC_WWAN_DNS_PRIORITY_HIGH (115)
/* These are to be set as route metric */
#define CC_WWAN_ROUTE_PRIORITY_LOW (1050)
--
2.32.0
From 0f38f32b98ae946b8259e3232e311d4f743acaba Mon Sep 17 00:00:00 2001
From: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>
Date: Mon, 6 Jul 2020 04:33:30 +0200
Subject: [PATCH 5/7] wwan: Fix signal strength display when extended signal
retrieval is disabled
MMModemSignal interface is used to retrieve extended signal information that
requires periodic polling. Therefore, it needs to be manually enabled in order
to use. There if a fallback to use mm_modem_get_signal_quality when MMModemSignal
interface is unavailable, but it didn't check whether it's actually enabled,
leaving the UI with empty label.
---
panels/wwan/cc-wwan-device.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/panels/wwan/cc-wwan-device.c b/panels/wwan/cc-wwan-device.c
index 31baff95c..55a627a5a 100644
--- a/panels/wwan/cc-wwan-device.c
+++ b/panels/wwan/cc-wwan-device.c
@@ -1183,12 +1183,16 @@ cc_wwan_device_dup_signal_string (CcWwanDevice *self)
GString *str;
gdouble value;
gboolean recent;
+ guint refresh_rate;
g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
modem_signal = mm_object_peek_modem_signal (self->mm_object);
- if (!modem_signal)
+ if (modem_signal)
+ refresh_rate = mm_modem_signal_get_rate (modem_signal);
+
+ if (!modem_signal || !refresh_rate)
return g_strdup_printf ("%d%%", mm_modem_get_signal_quality (self->modem, &recent));
str = g_string_new ("");
--
2.32.0
From 99bbe06a25a523898dde7370274430e7502be53e Mon Sep 17 00:00:00 2001
From: Mohammed Sadiq <sadiq@sadiqpk.org>
Date: Sat, 14 Aug 2021 13:39:33 +0530
Subject: [PATCH 6/7] wwan: Fix a typo
Fixes https://gitlab.gnome.org/GNOME/gnome-control-center/-/commit/dc840f0aec346f3fb297789eb1641255574c47a4#note_1249116
---
panels/wwan/cc-wwan-apn-dialog.ui | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/panels/wwan/cc-wwan-apn-dialog.ui b/panels/wwan/cc-wwan-apn-dialog.ui
index fb8432bc6..9ac07ce38 100644
--- a/panels/wwan/cc-wwan-apn-dialog.ui
+++ b/panels/wwan/cc-wwan-apn-dialog.ui
@@ -211,7 +211,7 @@
<property name="visible">1</property>
<property name="halign">end</property>
<property name="valign">center</property>
- <property name="label" translatable="yes">Passsword</property>
+ <property name="label" translatable="yes">Password</property>
<style>
<class name="dim-label" />
</style>
--
2.32.0
From b1cf8916b2569aa6c3b7f724eff37f198ed37b73 Mon Sep 17 00:00:00 2001
From: Mohammed Sadiq <sadiq@sadiqpk.org>
Date: Sat, 14 Aug 2021 13:54:33 +0530
Subject: [PATCH 7/7] wwan: Avoid translation of some strings
Many strings are not shown in the UI. Let's not overwhelm translators
---
panels/wwan/cc-wwan-errors-private.h | 50 ++++++++++++++--------------
1 file changed, 25 insertions(+), 25 deletions(-)
diff --git a/panels/wwan/cc-wwan-errors-private.h b/panels/wwan/cc-wwan-errors-private.h
index 761b82f35..076482d1f 100644
--- a/panels/wwan/cc-wwan-errors-private.h
+++ b/panels/wwan/cc-wwan-errors-private.h
@@ -39,12 +39,12 @@ typedef struct {
static ErrorTable me_errors[] = {
{ MM_MOBILE_EQUIPMENT_ERROR_PHONE_FAILURE, N_("Phone failure") },
{ MM_MOBILE_EQUIPMENT_ERROR_NO_CONNECTION, N_("No connection to phone") },
- { MM_MOBILE_EQUIPMENT_ERROR_LINK_RESERVED, N_("Phone-adaptor link reserved") },
+ { MM_MOBILE_EQUIPMENT_ERROR_LINK_RESERVED, "Phone-adaptor link reserved" },
{ MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED, N_("Operation not allowed") },
{ MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, N_("Operation not supported") },
- { MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN, N_("PH-SIM PIN required") },
- { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN, N_("PH-FSIM PIN required") },
- { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK, N_("PH-FSIM PUK required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN, "PH-SIM PIN required" },
+ { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN, "PH-FSIM PIN required" },
+ { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK, "PH-FSIM PUK required" },
{ MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED, N_("SIM not inserted") },
{ MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN, N_("SIM PIN required") },
{ MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK, N_("SIM PUK required") },
@@ -54,34 +54,34 @@ static ErrorTable me_errors[] = {
{ MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD, N_("Incorrect password") },
{ MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN2, N_("SIM PIN2 required") },
{ MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK2, N_("SIM PUK2 required") },
- { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FULL, N_("Memory full") },
- { MM_MOBILE_EQUIPMENT_ERROR_INVALID_INDEX, N_("Invalid index") },
+ { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FULL, "Memory full" },
+ { MM_MOBILE_EQUIPMENT_ERROR_INVALID_INDEX, "Invalid index" },
{ MM_MOBILE_EQUIPMENT_ERROR_NOT_FOUND, N_("Not found") },
- { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FAILURE, N_("Memory failure") },
+ { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FAILURE, "Memory failure" },
{ MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK, N_("No network service") },
{ MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, N_("Network timeout") },
- { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED, N_("Network not allowed - emergency calls only") },
- { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN, N_("Network personalization PIN required") },
- { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK, N_("Network personalization PUK required") },
- { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN, N_("Network subset personalization PIN required") },
- { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK, N_("Network subset personalization PUK required") },
- { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN, N_("Service provider personalization PIN required") },
- { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK, N_("Service provider personalization PUK required") },
- { MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN, N_("Corporate personalization PIN required") },
- { MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK, N_("Corporate personalization PUK required") },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED, "Network not allowed - emergency calls only" },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN, "Network personalization PIN required" },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK, "Network personalization PUK required" },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN, "Network subset personalization PIN required" },
+ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK, "Network subset personalization PUK required" },
+ { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN, "Service provider personalization PIN required" },
+ { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK, "Service provider personalization PUK required" },
+ { MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN, "Corporate personalization PIN required" },
+ { MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK, "Corporate personalization PUK required" },
{ MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, N_("Unknown error") },
- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_MS, N_("Illegal MS") },
- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_ME, N_("Illegal ME") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_MS, "Illegal MS" },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_ME, "Illegal ME" },
{ MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_NOT_ALLOWED, N_("GPRS services not allowed") },
- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PLMN_NOT_ALLOWED, N_("PLMN not allowed") },
- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_LOCATION_NOT_ALLOWED, N_("Location area not allowed") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PLMN_NOT_ALLOWED, "PLMN not allowed" },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_LOCATION_NOT_ALLOWED, "Location area not allowed" },
{ MM_MOBILE_EQUIPMENT_ERROR_GPRS_ROAMING_NOT_ALLOWED, N_("Roaming not allowed in this location area") },
- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUPPORTED, N_("Service option not supported") },
- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUBSCRIBED, N_("Requested service option not subscribed") },
- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_OUT_OF_ORDER, N_("Service option temporarily out of order") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUPPORTED, "Service option not supported" },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUBSCRIBED, "Requested service option not subscribed" },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_OUT_OF_ORDER, "Service option temporarily out of order" },
{ MM_MOBILE_EQUIPMENT_ERROR_GPRS_UNKNOWN, N_("Unspecified GPRS error") },
- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PDP_AUTH_FAILURE, N_("PDP authentication failure") },
- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_INVALID_MOBILE_CLASS, N_("Invalid mobile class") },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PDP_AUTH_FAILURE, "PDP authentication failure" },
+ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_INVALID_MOBILE_CLASS, "Invalid mobile class" },
};
static inline const gchar *
--
2.32.0