diff --git a/gnome-control-center.spec b/gnome-control-center.spec index 46b08b6..950ecbd 100644 --- a/gnome-control-center.spec +++ b/gnome-control-center.spec @@ -14,7 +14,7 @@ Name: gnome-control-center Version: 40.0 -Release: 13%{?dist} +Release: 14%{?dist} Summary: Utilities to configure the GNOME desktop License: GPLv2+ and CC-BY-SA @@ -29,6 +29,7 @@ Patch0: distro-logo.patch # https://bugzilla.redhat.com/show_bug.cgi?id=1952274 Patch1: gnome-control-center-Drop-the-unused-build-dependency-on-Grilo.patch Patch2: power-profiles-backport.patch +Patch3: wwan-backport-gnome-40.patch BuildRequires: chrpath BuildRequires: cups-devel @@ -73,6 +74,7 @@ BuildRequires: pkgconfig(upower-glib) >= %{upower_version} BuildRequires: pkgconfig(x11) BuildRequires: pkgconfig(xi) BuildRequires: pkgconfig(udisks2) +BuildRequires: pkgconfig(gcr-3) %ifnarch s390 s390x BuildRequires: pkgconfig(gnome-bluetooth-1.0) >= %{gnome_bluetooth_version} BuildRequires: pkgconfig(libwacom) @@ -215,6 +217,10 @@ chrpath --delete $RPM_BUILD_ROOT%{_bindir}/gnome-control-center %dir %{_datadir}/gnome/wm-properties %changelog +* Wed Aug 25 2021 Carlos Garnacho - 40.0-14 +- Backport WWAN panel + Resolves: #1995559 + * Fri Aug 20 2021 Carlos Garnacho - 40.0.13 - Backport power profile changes Resolves: #1994475 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 +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 +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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-apn-dialog" ++ ++#include ++#include ++#include ++ ++#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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#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 @@ ++ ++ ++ ++ ++ ++ ++ +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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-data" ++ ++#ifdef HAVE_CONFIG_H ++# include ++#endif ++ ++#define _GNU_SOURCE ++#include ++#include ++#include ++ ++#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 _("Error: Access denied changing settings"); ++ ++ if (self->error->domain == MM_MOBILE_EQUIPMENT_ERROR) ++ return _("Error: 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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-details-dialog" ++ ++#include ++#include ++#include ++ ++#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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#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 @@ ++ ++ ++ ++ +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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-device-page" ++ ++#include ++#include ++#include ++#define GCR_API_SUBJECT_TO_CHANGE ++#include ++ ++#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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++ ++#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 @@ ++ ++ ++ ++ ++ both ++ ++ ++ ++ ++ ++ ++ +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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * 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 ++#include ++#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) ++# include ++# include ++#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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-network-mode-dialog" ++ ++#include ++#include ++#include ++ ++#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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#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 @@ ++ ++ ++ ++ +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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-network-dialog" ++ ++#include ++#include ++#include ++ ++#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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#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 @@ ++ ++ ++ ++ +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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-panel" ++ ++#include ++#include ++#include ++ ++#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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++ ++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 @@ ++ ++ ++ ++ ++ ++ bottom ++ popover_arrow ++ ++ ++ 1 ++ none ++ ++ ++ ++ ++ ++ ++ ++ 1 ++ ++ ++ Enable Mobile Network ++ ++ ++ ++ +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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-sim-lock-dialog" ++ ++#include ++#include ++#include ++ ++#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 . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#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 @@ ++ ++ ++ ++ +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 @@ ++ ++ ++ ++ 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 ++ ++ +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 +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 +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 +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 +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 @@ + 1 + end + center +- Passsword ++ Password + +-- +2.32.0 + + +From b1cf8916b2569aa6c3b7f724eff37f198ed37b73 Mon Sep 17 00:00:00 2001 +From: Mohammed Sadiq +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 +