summaryrefslogtreecommitdiff
path: root/wwan-backport-gnome-40.patch
diff options
context:
space:
mode:
Diffstat (limited to 'wwan-backport-gnome-40.patch')
-rw-r--r--wwan-backport-gnome-40.patch9200
1 files changed, 9200 insertions, 0 deletions
diff --git a/wwan-backport-gnome-40.patch b/wwan-backport-gnome-40.patch
new file mode 100644
index 0000000..408607a
--- /dev/null
+++ b/wwan-backport-gnome-40.patch
@@ -0,0 +1,9200 @@
+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
+