diff options
Diffstat (limited to 'panels/wwan')
31 files changed, 8255 insertions, 0 deletions
diff --git a/panels/wwan/cc-wwan-apn-dialog.c b/panels/wwan/cc-wwan-apn-dialog.c new file mode 100644 index 0000000..a009223 --- /dev/null +++ b/panels/wwan/cc-wwan-apn-dialog.c @@ -0,0 +1,428 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-apn-dialog.c + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-wwan-apn-dialog" + +#include <config.h> +#include <glib/gi18n.h> +#include <libmm-glib.h> + +#include "cc-wwan-device.h" +#include "cc-wwan-data.h" +#include "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; + GtkCheckButton *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; + GtkCheckButton *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_editable_set_text (GTK_EDITABLE (self->name_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (self->apn_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (self->username_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (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_editable_get_text (GTK_EDITABLE (self->name_entry)); + apn_name = gtk_editable_get_text (GTK_EDITABLE (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_editable_get_text (GTK_EDITABLE (self->username_entry))); + cc_wwan_data_apn_set_password (apn, gtk_editable_get_text (GTK_EDITABLE (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_editable_get_text (GTK_EDITABLE (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_editable_get_text (GTK_EDITABLE (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_check_button_set_active (GTK_CHECK_BUTTON (row->radio_button), TRUE); +} + +static void +cc_wwan_apn_changed_cb (CcWwanApnDialog *self, + GtkWidget *widget) +{ + CcWwanApnRow *row; + + if (!gtk_check_button_get_active (GTK_CHECK_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_editable_set_text (GTK_EDITABLE (self->name_entry), cc_wwan_data_apn_get_name (apn)); + gtk_editable_set_text (GTK_EDITABLE (self->apn_entry), cc_wwan_data_apn_get_apn (apn)); + gtk_editable_set_text (GTK_EDITABLE (self->username_entry), cc_wwan_data_apn_get_username (apn)); + gtk_editable_set_text (GTK_EDITABLE (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_check_button_new (); + row->radio_button = GTK_CHECK_BUTTON (radio); + gtk_check_button_set_group (row->radio_button, self->apn_radio_button); + 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_check_button_set_active (GTK_CHECK_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_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END); + 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_label_set_ellipsize (GTK_LABEL (apn_label), PANGO_ELLIPSIZE_END); + 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_accessible_update_property (GTK_ACCESSIBLE (edit_button), + GTK_ACCESSIBLE_PROPERTY_LABEL, _("Edit"), + -1); + 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_list_box_row_set_child (GTK_LIST_BOX_ROW (row), grid); + + 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_set_sensitive (GTK_WIDGET (self->save_button), FALSE); + 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 0000000..37e905e --- /dev/null +++ b/panels/wwan/cc-wwan-apn-dialog.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-apn-dialog.h + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <adwaita.h> +#include <shell/cc-panel.h> + +#include "cc-wwan-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WWAN_APN_DIALOG (cc_wwan_apn_dialog_get_type()) +G_DECLARE_FINAL_TYPE (CcWwanApnDialog, cc_wwan_apn_dialog, CC, WWAN_APN_DIALOG, GtkDialog) + +CcWwanApnDialog *cc_wwan_apn_dialog_new (GtkWindow *parent_window, + CcWwanDevice *device); + +G_END_DECLS diff --git a/panels/wwan/cc-wwan-apn-dialog.ui b/panels/wwan/cc-wwan-apn-dialog.ui new file mode 100644 index 0000000..7853756 --- /dev/null +++ b/panels/wwan/cc-wwan-apn-dialog.ui @@ -0,0 +1,209 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk" version="4.0"/> + <template class="CcWwanApnDialog" parent="GtkDialog"> + <property name="default-height">480</property> + <property name="default-width">360</property> + <property name="hide-on-close">True</property> + <property name="title" translatable="yes">Access Points</property> + <property name="modal">True</property> + + <child type="titlebar"> + <object class="GtkHeaderBar"> + + <!-- Back button --> + <child type="start"> + <object class="GtkButton" id="back_button"> + <property name="use-underline">True</property> + <property name="icon-name">go-previous-symbolic</property> + <signal name="clicked" handler="cc_wwan_apn_back_clicked_cb" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Back</property> + </accessibility> + </object> + </child> + + <!-- Add button --> + <child type="end"> + <object class="GtkButton" id="add_button"> + <property name="use-underline">True</property> + <property name="icon-name">list-add-symbolic</property> + <signal name="clicked" handler="cc_wwan_apn_add_clicked_cb" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Add</property> + </accessibility> + </object> + </child> + + <!-- Save button --> + <child type="end"> + <object class="GtkButton" id="save_button"> + <property name="visible">False</property> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Save</property> + <signal name="clicked" handler="cc_wwan_apn_save_clicked_cb" swapped="yes"/> + <style> + <class name="default"/> + </style> + </object> + </child> + + </object> + </child> + + <child> + <object class="GtkStack" id="apn_settings_stack"> + <property name="transition-type">slide-left-right</property> + + <!-- Access Point List --> + <child> + <object class="AdwPreferencesPage" id="apn_list_view"> + <child> + <object class="AdwPreferencesGroup"> + <child> + <object class="GtkListBox" id="apn_list"> + <property name="valign">start</property> + <property name="selection-mode">none</property> + <signal name="row-activated" handler="cc_wwan_apn_activated_cb" swapped="yes"/> + <style> + <class name="boxed-list"/> + </style> + </object> + </child> + </object> + </child> + + </object> + </child> + + <child> + <object class="AdwPreferencesPage" id="apn_edit_view"> + <child> + <object class="AdwPreferencesGroup"> + <child> + <object class="GtkGrid"> + <property name="orientation">vertical</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="row-spacing">6</property> + <property name="column-spacing">12</property> + + <!-- Name --> + <child> + <object class="GtkLabel"> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Name</property> + <property name="mnemonic_widget">name_entry</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="name_entry"> + <property name="hexpand">True</property> + <signal name="changed" handler="cc_wwan_apn_entry_changed_cb" swapped="yes"/> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + + <!-- APN --> + <child> + <object class="GtkLabel"> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">APN</property> + <property name="mnemonic_widget">apn_entry</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="apn_entry"> + <property name="margin-bottom">12</property> + <signal name="changed" handler="cc_wwan_apn_entry_changed_cb" swapped="yes"/> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + + <!-- Username --> + <child> + <object class="GtkLabel"> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Username</property> + <property name="mnemonic_widget">username_entry</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="username_entry"> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + + <!-- Password --> + <child> + <object class="GtkLabel"> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Password</property> + <property name="mnemonic_widget">password_entry</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="password_entry"> + <property name="margin-bottom">12</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + </layout> + </object> + </child> + + </object> + </child> + </object> <!-- ./AdwPreferencesGroup --> + </child> + </object> + </child> + + </object> <!-- ./GtkStack apn_settings_stack --> + </child> + </template> + + <!-- A simple hack to create a radio button group --> + <object class="GtkCheckButton" id="apn_radio_button"/> +</interface> diff --git a/panels/wwan/cc-wwan-data.c b/panels/wwan/cc-wwan-data.c new file mode 100644 index 0000000..2608ab2 --- /dev/null +++ b/panels/wwan/cc-wwan-data.c @@ -0,0 +1,1502 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-data.c + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-wwan-data" + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#define _GNU_SOURCE +#include <string.h> +#include <glib/gi18n.h> +#include <nma-mobile-providers.h> + +#include "cc-wwan-data.h" + +/** + * @short_description: Device Internet Data Object + * @include: "cc-wwan-device-data.h" + * + * #CcWwanData represents the data object of the given + * #CcWwanDevice. Please note that while #CcWWanDevice + * is bound to the hardware device, #CcWwanData may also + * depend on the inserted SIM (if supported). So the state + * of #CcWwanData changes when SIM is changed. + */ + +/* + * Priority for connections. 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) +#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 */ + gboolean apn_list_updated; /* APN list updated from mobile-provider-info */ +}; + +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 (CcWwanDataApn *apn, + NMAMobileAccessMethod *access_method) +{ + NMConnection *connection; + NMSetting *setting; + + if (!apn->remote_connection) + return FALSE; + + connection = NM_CONNECTION (apn->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), + cc_wwan_data_apn_get_password (apn)) != 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, 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 || self->apn_list_updated) + return; + + if (!self->apn_list) + return; + + 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); + + self->apn_list_updated = TRUE; + + 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 (); + apn->access_method = l->data; + 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 || !self->nm_device || + nm_device_get_state (self->nm_device) <= NM_DEVICE_STATE_UNAVAILABLE) + 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 +wwan_device_3gpp_operator_code_changd_cb (CcWwanData *self) +{ + MMModem3gpp *modem_3gpp; + + modem_3gpp = mm_object_peek_modem_3gpp (self->mm_object); + + if (!self->operator_code) + { + self->operator_code = mm_modem_3gpp_dup_operator_code (modem_3gpp); + + if (self->operator_code) + { + wwan_data_update_apn_list (self); + wwan_data_update_apn_list_db (self); + } + } +} + +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) + { + g_signal_connect_object (modem_3gpp, "notify::operator-code", + G_CALLBACK (wwan_device_3gpp_operator_code_changd_cb), + self, G_CONNECT_SWAPPED); + wwan_device_3gpp_operator_code_changd_cb (self); + } + } + + if (self->active_connection) + g_object_ref (self->active_connection); + + g_signal_connect_object (self->nm_device, "notify::state", + G_CALLBACK (wwan_device_state_changed_cb), + self, G_CONNECT_SWAPPED); + + wwan_data_update_apn_list (self); + wwan_data_update_apn_list_db (self); + + return self; +} + +GError * +cc_wwan_data_get_error (CcWwanData *self) +{ + g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); + + return self->error; +} + +const gchar * +cc_wwan_data_get_simple_html_error (CcWwanData *self) +{ + g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); + + if (!self->error) + return NULL; + + if (g_error_matches (self->error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return _("Operation Cancelled"); + + if (g_error_matches (self->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED)) + return _("<b>Error:</b> Access denied changing settings"); + + if (self->error->domain == MM_MOBILE_EQUIPMENT_ERROR) + return _("<b>Error:</b> Mobile Equipment Error"); + + return NULL; +} + +GListModel * +cc_wwan_data_get_apn_list (CcWwanData *self) +{ + g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); + + if (!self->apn_list) + wwan_data_update_apn_list (self); + + return G_LIST_MODEL (self->apn_list); +} + +static gboolean +wwan_data_apn_is_new (CcWwanDataApn *apn) +{ + return apn->remote_connection == NULL; +} + +static void +wwan_data_update_apn (CcWwanData *self, + CcWwanDataApn *apn, + NMConnection *connection) +{ + NMSetting *setting; + const gchar *name, *username, *password, *apn_name; + gint dns_priority, route_metric; + + setting = NM_SETTING (nm_connection_get_setting_connection (connection)); + + g_object_set (setting, + NM_SETTING_CONNECTION_AUTOCONNECT, self->data_enabled, + NULL); + + setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); + + g_object_set (setting, + NM_SETTING_GSM_HOME_ONLY, self->home_only, + NULL); + + setting = NM_SETTING (nm_connection_get_setting_ip4_config (connection)); + if (self->priority == CC_WWAN_APN_PRIORITY_HIGH && + self->default_apn == apn) + { + dns_priority = CC_WWAN_DNS_PRIORITY_HIGH; + route_metric = CC_WWAN_ROUTE_PRIORITY_HIGH; + } + else + { + dns_priority = CC_WWAN_DNS_PRIORITY_LOW; + route_metric = CC_WWAN_ROUTE_PRIORITY_LOW; + } + + g_object_set (setting, + NM_SETTING_IP_CONFIG_DNS_PRIORITY, dns_priority, + NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64)route_metric, + NULL); + + if (apn->access_method && !apn->remote_connection) + { + name = nma_mobile_access_method_get_name (apn->access_method); + username = nma_mobile_access_method_get_username (apn->access_method); + password = nma_mobile_access_method_get_password (apn->access_method); + apn_name = nma_mobile_access_method_get_3gpp_apn (apn->access_method); + } + else + { + return; + } + + setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); + g_object_set (setting, + NM_SETTING_GSM_USERNAME, username, + NM_SETTING_GSM_PASSWORD, password, + NM_SETTING_GSM_APN, apn_name, + NULL); + + setting = NM_SETTING (nm_connection_get_setting_connection (connection)); + + g_object_set (setting, + NM_SETTING_CONNECTION_ID, name, + NULL); +} + +static gint +wwan_data_get_apn_index (CcWwanData *self, + CcWwanDataApn *apn) +{ + GListModel *model; + guint i, n_items; + + model = G_LIST_MODEL (self->apn_list); + n_items = g_list_model_get_n_items (model); + + for (i = 0; i < n_items; i++) + { + g_autoptr(CcWwanDataApn) cached_apn = NULL; + + cached_apn = g_list_model_get_item (model, i); + + if (apn == cached_apn) + return i; + } + + return -1; +} + +static void +cc_wwan_data_connection_updated_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + CcWwanData *self; + CcWwanDataApn *apn; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + + self = g_task_get_source_object (G_TASK (task)); + apn = g_task_get_task_data (G_TASK (task)); + + nm_remote_connection_commit_changes_finish (apn->remote_connection, + result, &error); + if (!error) + { + guint apn_index; + apn_index = wwan_data_get_apn_index (self, apn); + + if (apn_index >= 0) + g_list_model_items_changed (G_LIST_MODEL (self->apn_list), + apn_index, 1, 1); + else + g_warning ("APN ‘%s’ not in APN list", + cc_wwan_data_apn_get_name (apn)); + + apn->modified = FALSE; + g_task_return_boolean (task, TRUE); + } + else + { + g_task_return_error (task, g_steal_pointer (&error)); + } +} + +static void +cc_wwan_data_new_connection_added_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + CcWwanData *self; + CcWwanDataApn *apn; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + + self = g_task_get_source_object (G_TASK (task)); + apn = g_task_get_task_data (G_TASK (task)); + apn->remote_connection = nm_client_add_connection_finish (self->nm_client, + result, &error); + if (!error) + { + apn->modified = FALSE; + + /* If APN has access method, it’s already on the list */ + if (!apn->access_method) + { + g_list_store_append (self->apn_list, apn); + g_object_unref (apn); + } + + g_task_return_pointer (task, apn, NULL); + } + else + { + g_task_return_error (task, g_steal_pointer (&error)); + } +} + +void +cc_wwan_data_save_apn (CcWwanData *self, + CcWwanDataApn *apn, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMConnection *connection = NULL; + g_autoptr(GTask) task = NULL; + + g_return_if_fail (CC_IS_WWAN_DATA (self)); + g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, apn, NULL); + + connection = wwan_data_get_nm_connection (apn); + + /* If the item has a remote connection, it should already be saved. + * We should save it again only if it got modified */ + if (apn->remote_connection && !apn->modified) + { + g_task_return_pointer (task, apn, NULL); + return; + } + + wwan_data_update_apn (self, apn, connection); + if (wwan_data_apn_is_new (apn)) + { + nm_client_add_connection_async (self->nm_client, apn->nm_connection, + TRUE, cancellable, + cc_wwan_data_new_connection_added_cb, + g_steal_pointer (&task)); + } + else + { + nm_remote_connection_commit_changes_async (apn->remote_connection, TRUE, + cancellable, + cc_wwan_data_connection_updated_cb, + g_steal_pointer (&task)); + } +} + +CcWwanDataApn * +cc_wwan_data_save_apn_finish (CcWwanData *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +cc_wwan_data_activated_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + CcWwanData *self; + NMActiveConnection *connection; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + + self = g_task_get_source_object (G_TASK (task)); + connection = nm_client_activate_connection_finish (self->nm_client, + result, &error); + if (connection) + { + g_set_object (&self->active_connection, connection); + g_task_return_boolean (task, TRUE); + } + else + { + g_task_return_error (task, g_steal_pointer (&error)); + } + + if (error) + g_warning ("Error: %s", error->message); +} + +static void +cc_wwan_data_disconnect_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + CcWwanData *self; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + + self = g_task_get_source_object (G_TASK (task)); + if (nm_device_disconnect_finish (self->nm_device, result, &error)) + { + g_clear_object (&self->active_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 + { + nm_device_disconnect_async (self->nm_device, + cancellable, + cc_wwan_data_disconnect_cb, + g_steal_pointer (&task)); + } +} + +/** + * 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); + + 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)); + + if (self->sim_id) + 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 : ""; +} + +/** + * 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 0000000..9572b86 --- /dev/null +++ b/panels/wwan/cc-wwan-data.h @@ -0,0 +1,93 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-data.h + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <glib-object.h> +#include <libmm-glib.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +#define CC_WWAN_APN_PRIORITY_LOW (1) +#define CC_WWAN_APN_PRIORITY_HIGH (2) + +#define CC_TYPE_WWAN_DATA_APN (cc_wwan_data_apn_get_type()) +G_DECLARE_FINAL_TYPE (CcWwanDataApn, cc_wwan_data_apn, CC, WWAN_DATA_APN, GObject) + +#define CC_TYPE_WWAN_DATA (cc_wwan_data_get_type()) +G_DECLARE_FINAL_TYPE (CcWwanData, cc_wwan_data, CC, WWAN_DATA, GObject) + +CcWwanData *cc_wwan_data_new (MMObject *mm_object, + NMClient *nm_client); +GError *cc_wwan_data_get_error (CcWwanData *self); +const gchar *cc_wwan_data_get_simple_html_error (CcWwanData *self); +GListModel *cc_wwan_data_get_apn_list (CcWwanData *self); +void cc_wwan_data_save_apn (CcWwanData *self, + CcWwanDataApn *apn, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +CcWwanDataApn *cc_wwan_data_save_apn_finish (CcWwanData *self, + GAsyncResult *result, + GError **error); +void cc_wwan_data_save_settings (CcWwanData *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean cc_wwan_data_save_settings_finish (CcWwanData *self, + GAsyncResult *result, + GError **error); +gboolean cc_wwan_data_delete_apn (CcWwanData *self, + CcWwanDataApn *apn, + GCancellable *cancellable, + GError **error); +gboolean cc_wwan_data_set_default_apn (CcWwanData *self, + CcWwanDataApn *apn); +CcWwanDataApn *cc_wwan_data_get_default_apn (CcWwanData *self); +gboolean cc_wwan_data_get_enabled (CcWwanData *self); +void cc_wwan_data_set_enabled (CcWwanData *self, + gboolean enabled); +gboolean cc_wwan_data_get_roaming_enabled (CcWwanData *self); +void cc_wwan_data_set_roaming_enabled (CcWwanData *self, + gboolean enable_roaming); + +CcWwanDataApn *cc_wwan_data_apn_new (void); +const gchar *cc_wwan_data_apn_get_name (CcWwanDataApn *apn); +void cc_wwan_data_apn_set_name (CcWwanDataApn *apn, + const gchar *name); +const gchar *cc_wwan_data_apn_get_apn (CcWwanDataApn *apn); +void cc_wwan_data_apn_set_apn (CcWwanDataApn *apn, + const gchar *apn_name); +const gchar *cc_wwan_data_apn_get_username (CcWwanDataApn *apn); +void cc_wwan_data_apn_set_username (CcWwanDataApn *apn, + const gchar *username); +const gchar *cc_wwan_data_apn_get_password (CcWwanDataApn *apn); +void cc_wwan_data_apn_set_password (CcWwanDataApn *apn, + const gchar *password); +gint cc_wwan_data_get_priority (CcWwanData *self); +void cc_wwan_data_set_priority (CcWwanData *self, + int priority); + +G_END_DECLS diff --git a/panels/wwan/cc-wwan-details-dialog.c b/panels/wwan/cc-wwan-details-dialog.c new file mode 100644 index 0000000..8f2f27d --- /dev/null +++ b/panels/wwan/cc-wwan-details-dialog.c @@ -0,0 +1,256 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-network-dialog.c + * + * Copyright 2019,2022 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-wwan-details-dialog" + +#include <config.h> +#include <glib/gi18n.h> +#include <libmm-glib.h> + +#include "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 0000000..3144eee --- /dev/null +++ b/panels/wwan/cc-wwan-details-dialog.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-details-dialog.h + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <adwaita.h> +#include <shell/cc-panel.h> + +#include "cc-wwan-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WWAN_DETAILS_DIALOG (cc_wwan_details_dialog_get_type()) +G_DECLARE_FINAL_TYPE (CcWwanDetailsDialog, cc_wwan_details_dialog, CC, WWAN_DETAILS_DIALOG, GtkDialog) + +CcWwanDetailsDialog *cc_wwan_details_dialog_new (GtkWindow *parent_window, + CcWwanDevice *device); + +G_END_DECLS diff --git a/panels/wwan/cc-wwan-details-dialog.ui b/panels/wwan/cc-wwan-details-dialog.ui new file mode 100644 index 0000000..79ae481 --- /dev/null +++ b/panels/wwan/cc-wwan-details-dialog.ui @@ -0,0 +1,274 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk" version="4.0"/> + <template class="CcWwanDetailsDialog" parent="GtkDialog"> + <property name="title" translatable="yes">Modem Details</property> + <property name="default-height">480</property> + <property name="default-width">360</property> + <property name="hide-on-close">True</property> + <property name="modal">True</property> + + <child> + <object class="AdwPreferencesPage"> + + <child> + <object class="AdwPreferencesGroup"> + <property name="title" translatable="yes">Modem Status</property> + <child> + <object class="GtkGrid"> + <property name="row-spacing">9</property> + <property name="column-spacing">6</property> + + <!-- Carrier --> + <child> + <object class="GtkLabel" id="carrier_label"> + <property name="label" translatable="yes">Carrier</property> + <property name="xalign">1.0</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="operator_name"> + <property name="xalign">0.0</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + + <!-- Network Type --> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Network Type</property> + <property name="xalign">1.0</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="network_type"> + <property name="xalign">0.0</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + + <!-- Signal Strength --> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Signal Strength</property> + <property name="xalign">1.0</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="signal_strength"> + <property name="xalign">0.0</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + + <!-- Network Status --> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Network Status</property> + <property name="xalign">1.0</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="network_status"> + <property name="xalign">0.0</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + </layout> + </object> + </child> + + <!-- Own Numbers --> + <child> + <object class="GtkLabel"> + <property name="visible" bind-source="own_numbers" bind-flags="sync-create"/> + <property name="label" translatable="yes">Own Number</property> + <property name="xalign">1.0</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">4</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="own_numbers"> + <property name="xalign">0.0</property> + <layout> + <property name="column">1</property> + <property name="row">4</property> + </layout> + </object> + </child> + + </object> + </child> + </object> + </child> + + <!-- Device Details Content --> + <child> + <object class="AdwPreferencesGroup"> + <property name="title" translatable="yes">Device Details</property> + <child> + <object class="GtkGrid"> + <property name="row-spacing">9</property> + <property name="column-spacing">6</property> + + <!-- Manufacturer --> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Manufacturer</property> + <property name="xalign">1.0</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="manufacturer"> + <property name="xalign">0.0</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + + <!-- Model --> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Model</property> + <property name="xalign">1.0</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="device_model"> + <property name="xalign">0.0</property> + <property name="selectable">True</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + + <!-- Firmware version --> + <child> + <object class="GtkLabel" id="firmware_label"> + <property name="label" translatable="yes">Firmware Version</property> + <property name="xalign">1.0</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="firmware_version"> + <property name="selectable">True</property> + <property name="xalign">0.0</property> + <property name="ellipsize">end</property> + <property name="wrap">True</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + + <!-- IMEI/ICCID --> + <child> + <object class="GtkLabel" id="identifier_label"> + <property name="label" translatable="yes">IMEI</property> + <property name="xalign">1.0</property> + <style> + <class name="dim-label"/> + </style> + <layout> + <property name="column">0</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="device_identifier"> + <property name="selectable">True</property> + <property name="xalign">0.0</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + </layout> + </object> + </child> + + </object> + </child> + </object> + </child> + </object> + </child> + + </template> + <object class="GtkSizeGroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="carrier_label"/> + <widget name="firmware_label"/> + </widgets> + </object> +</interface> diff --git a/panels/wwan/cc-wwan-device-page.c b/panels/wwan/cc-wwan-device-page.c new file mode 100644 index 0000000..13aa87a --- /dev/null +++ b/panels/wwan/cc-wwan-device-page.c @@ -0,0 +1,650 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-device-page.c + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-wwan-device-page" + +#include <config.h> +#include <glib/gi18n.h> +#include <libmm-glib.h> +#define GCR_API_SUBJECT_TO_CHANGE +#include <gcr/gcr-base.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; + + AdwToastOverlay *toast_overlay; + + 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_device_page_handle_data_row (CcWwanDevicePage *self, + CcListRow *data_row) +{ + gboolean active; + + /* 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 gboolean +wwan_apn_dialog_closed_cb (CcWwanDevicePage *self) +{ + CcListRow *data_row; + + if (gtk_widget_in_destruction (GTK_WIDGET (self))) + return FALSE; + + data_row = g_object_get_data (G_OBJECT (self->apn_dialog), "row"); + g_object_set_data (G_OBJECT (self->apn_dialog), "row", NULL); + + if (data_row) + wwan_device_page_handle_data_row (self, data_row); + + return FALSE; +} + +static void +wwan_data_show_apn_dialog (CcWwanDevicePage *self) +{ + GtkWindow *top_level; + + top_level = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))); + + if (!self->apn_dialog) + { + self->apn_dialog = cc_wwan_apn_dialog_new (top_level, self->device); + g_signal_connect_object (self->apn_dialog, "unmap", + G_CALLBACK (wwan_apn_dialog_closed_cb), + self, G_CONNECT_SWAPPED); + } + + 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) +{ + 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); + g_object_set_data (G_OBJECT (self->apn_dialog), "row", data_row); + } + else + { + wwan_device_page_handle_data_row (self, data_row); + } +} + +static void +wwan_network_settings_activated_cb (CcWwanDevicePage *self, + CcListRow *row) +{ + GtkWidget *dialog; + GtkWindow *top_level; + + top_level = GTK_WINDOW (gtk_widget_get_root (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_root (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 ((GtkWindow **)&self->apn_dialog, gtk_window_destroy); + g_clear_pointer ((GtkWindow **)&self->details_dialog, gtk_window_destroy); + g_clear_pointer ((GtkWindow **)&self->network_mode_dialog, gtk_window_destroy); + g_clear_pointer ((GtkWindow **)&self->network_dialog, gtk_window_destroy); + g_clear_pointer ((GtkWindow **)&self->sim_lock_dialog, gtk_window_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)); +} + +static void +cc_wwan_error_changed_cb (CcWwanDevicePage *self) +{ + AdwToast *toast; + const gchar *message; + + message = cc_wwan_device_get_simple_error (self->device); + + if (!message) + return; + + toast = adw_toast_new (message); + adw_toast_overlay_add_toast (self->toast_overlay, toast); +} + +CcWwanDevicePage * +cc_wwan_device_page_new (CcWwanDevice *device, + GtkWidget *toast_overlay) +{ + 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->toast_overlay = ADW_TOAST_OVERLAY (toast_overlay); + + 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 0000000..0fd03c1 --- /dev/null +++ b/panels/wwan/cc-wwan-device-page.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-device-page.h + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <shell/cc-panel.h> + +#include "cc-wwan-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WWAN_DEVICE_PAGE (cc_wwan_device_page_get_type()) +G_DECLARE_FINAL_TYPE (CcWwanDevicePage, cc_wwan_device_page, CC, WWAN_DEVICE_PAGE, GtkBox) + +CcWwanDevicePage *cc_wwan_device_page_new (CcWwanDevice *device, + GtkWidget *toast_overlay); +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 0000000..9899920 --- /dev/null +++ b/panels/wwan/cc-wwan-device-page.ui @@ -0,0 +1,190 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk" version="4.0"/> + <template class="CcWwanDevicePage" parent="GtkBox"> + <child> + <object class="GtkStack" id="main_stack"> + <property name="vhomogeneous">False</property> + <property name="hhomogeneous">False</property> + + <!-- SIM not inserted view --> + <child> + <object class="GtkStackPage"> + <property name="name">no-sim-view</property> + <property name="child"> + <object class="AdwStatusPage"> + <property name="hexpand">True</property> + <property name="icon-name">auth-sim-missing</property> + <property name="title" translatable="yes">No SIM</property> + <property name="description" translatable="yes">Insert a SIM card to use this modem</property> + </object> + </property> + </object> + </child> + + <!-- SIM locked view --> + <child> + <object class="GtkStackPage"> + <property name="name">sim-lock-view</property> + <property name="child"> + <object class="AdwStatusPage"> + <property name="hexpand">True</property> + <property name="icon-name">auth-sim-locked</property> + <property name="title" translatable="yes">SIM Locked</property> + <child> + <object class="GtkButton" id="unlock_button"> + <property name="halign">center</property> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Unlock</property> + <signal name="clicked" handler="wwan_device_unlock_clicked_cb" swapped="yes"/> + <style> + <class name="suggested-action"/> + </style> + </object> + </child> + </object> + </property> + </object> + </child> + + <!-- Network Settings --> + <child> + <object class="GtkStackPage"> + <property name="name">settings-view</property> + <property name="child"> + <object class="GtkBox"> + <property name="margin-top">18</property> + <property name="orientation">vertical</property> + + <child> + <object class="AdwPreferencesGroup"> + <property name="title" translatable="yes">Network</property> + <property name="margin-bottom">32</property> + + <!-- Internet settings --> + <child> + <object class="GtkListBox" id="data_settings_list"> + <property name="selection-mode">none</property> + <style> + <class name="boxed-list"/> + </style> + + <!-- Enable/Disable Data --> + <child> + <object class="CcListRow" id="data_enable_row"> + <property name="use-underline">True</property> + <property name="show-switch">True</property> + <property name="title" translatable="yes">_Mobile Data</property> + <property name="subtitle" translatable="yes">Access data using mobile network</property> + <signal name="notify::active" handler="wwan_data_settings_changed_cb" swapped="yes"/> + </object> + </child> + + <!-- Data Roaming --> + <child> + <object class="CcListRow" id="data_roaming_row"> + <property name="use-underline">True</property> + <property name="show-switch">True</property> + <property name="title" translatable="yes">_Data Roaming</property> + <property name="subtitle" translatable="yes">Use mobile data when roaming</property> + <signal name="notify::active" handler="wwan_data_settings_changed_cb" swapped="yes"/> + </object> + </child> + + </object> + </child> + + </object> + </child> + + <!-- Network Settings --> + <child> + <object class="AdwPreferencesGroup"> + <property name="margin-bottom">32</property> + <child> + <object class="GtkListBox" id="network_settings_list"> + <property name="selection-mode">none</property> + <signal name="row-activated" handler="wwan_network_settings_activated_cb" swapped="yes"/> + <style> + <class name="boxed-list"/> + </style> + <child> + <object class="CcListRow" id="network_mode_row"> + <property name="use-underline">True</property> + <property name="show-arrow">True</property> + <property name="title" translatable="yes">_Network Mode</property> + </object> + </child> + <child> + <object class="CcListRow" id="network_name_row"> + <property name="use-underline">True</property> + <property name="show-arrow">True</property> + <property name="title" translatable="yes">N_etwork</property> + </object> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="AdwPreferencesGroup"> + <property name="title" translatable="yes">Advanced</property> + <child> + <object class="GtkListBox" id="advanced_settings_list"> + <property name="selection-mode">none</property> + <style> + <class name="boxed-list"/> + </style> + <signal name="row-activated" handler="wwan_advanced_settings_activated_cb" swapped="yes"/> + + <!-- Accesss Point Settings --> + <child> + <object class="CcListRow" id="apn_settings_row"> + <property name="use-underline">True</property> + <property name="show-arrow">true</property> + <property name="title" translatable="yes">_Access Point Names</property> + </object> + </child> + + <!-- SIM Lock --> + <child> + <object class="CcListRow" id="sim_lock_row"> + <property name="use-underline">True</property> + <property name="show-arrow">True</property> + <property name="title" translatable="yes">_SIM Lock</property> + <property name="subtitle" translatable="yes">Lock SIM with PIN</property> + </object> + </child> + + <!-- Modem Details --> + <child> + <object class="CcListRow" id="details_row"> + <property name="use-underline">True</property> + <property name="show-arrow">True</property> + <property name="title" translatable="yes">M_odem Details</property> + </object> + </child> + + </object> + </child> + </object> + </child> + + </object> + </property> + </object> + </child> + + </object> <!-- ./GtkStack main_stack --> + </child> + </template> + <object class="GtkSizeGroup"> + <property name="mode">both</property> + <widgets> + <widget name="apn_settings_row"/> + <widget name="sim_lock_row"/> + <widget name="details_row"/> + </widgets> + </object> +</interface> diff --git a/panels/wwan/cc-wwan-device.c b/panels/wwan/cc-wwan-device.c new file mode 100644 index 0000000..a77715b --- /dev/null +++ b/panels/wwan/cc-wwan-device.c @@ -0,0 +1,1481 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-device.c + * + * Copyright 2019-2020 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-wwan-device" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib/gi18n.h> +#include <polkit/polkit.h> +#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) +# include <NetworkManager.h> +# include <nma-mobile-providers.h> +#endif + +#include "cc-wwan-errors-private.h" +#include "cc-wwan-device.h" + +/** + * @short_description: Device Object + * @include: "cc-wwan-device.h" + */ + +struct _CcWwanDevice +{ + GObject parent_instance; + + MMObject *mm_object; + MMModem *modem; + MMSim *sim; + MMModem3gpp *modem_3gpp; + + const char *operator_code; /* MCCMNC */ + GError *error; + + /* Building with NetworkManager is optional, + * so #NMclient type can’t be used here. + */ + GObject *nm_client; /* An #NMClient */ + CcWwanData *wwan_data; + + gulong modem_3gpp_id; + gulong modem_3gpp_locks_id; + + /* Enabled locks like PIN, PIN2, PUK, etc. */ + MMModem3gppFacility locks; + + CcWwanState registration_state; + gboolean network_is_manual; +}; + +G_DEFINE_TYPE (CcWwanDevice, cc_wwan_device, G_TYPE_OBJECT) + + +enum { + PROP_0, + PROP_OPERATOR_NAME, + PROP_ENABLED_LOCKS, + PROP_ERROR, + PROP_HAS_DATA, + PROP_NETWORK_MODE, + PROP_REGISTRATION_STATE, + PROP_SIGNAL, + PROP_UNLOCK_REQUIRED, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static void +cc_wwan_device_state_changed_cb (CcWwanDevice *self) +{ + MMModem3gppRegistrationState state; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_OPERATOR_NAME]); + + state = mm_modem_3gpp_get_registration_state (self->modem_3gpp); + + switch (state) + { + case MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN: + self->registration_state = CC_WWAN_REGISTRATION_STATE_UNKNOWN; + break; + + case MM_MODEM_3GPP_REGISTRATION_STATE_DENIED: + self->registration_state = CC_WWAN_REGISTRATION_STATE_DENIED; + break; + + case MM_MODEM_3GPP_REGISTRATION_STATE_IDLE: + self->registration_state = CC_WWAN_REGISTRATION_STATE_IDLE; + break; + + case MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING: + self->registration_state = CC_WWAN_REGISTRATION_STATE_SEARCHING; + break; + + case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING: + self->registration_state = CC_WWAN_REGISTRATION_STATE_ROAMING; + break; + + default: + self->registration_state = CC_WWAN_REGISTRATION_STATE_REGISTERED; + break; + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REGISTRATION_STATE]); +} + +static void +cc_wwan_device_locks_changed_cb (CcWwanDevice *self) +{ + self->locks = mm_modem_3gpp_get_enabled_facility_locks (self->modem_3gpp); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLED_LOCKS]); +} + +static void +cc_wwan_device_3gpp_changed_cb (CcWwanDevice *self) +{ + gulong handler_id = 0; + + if (self->modem_3gpp_id) + g_signal_handler_disconnect (self->modem_3gpp, self->modem_3gpp_id); + self->modem_3gpp_id = 0; + + if (self->modem_3gpp_locks_id) + g_signal_handler_disconnect (self->modem_3gpp, self->modem_3gpp_locks_id); + self->modem_3gpp_locks_id = 0; + + g_clear_object (&self->modem_3gpp); + self->modem_3gpp = mm_object_get_modem_3gpp (self->mm_object); + + if (self->modem_3gpp) + { + handler_id = g_signal_connect_object (self->modem_3gpp, "notify::registration-state", + G_CALLBACK (cc_wwan_device_state_changed_cb), + self, G_CONNECT_SWAPPED); + self->modem_3gpp_id = handler_id; + + handler_id = g_signal_connect_object (self->modem_3gpp, "notify::enabled-facility-locks", + G_CALLBACK (cc_wwan_device_locks_changed_cb), + self, G_CONNECT_SWAPPED); + self->modem_3gpp_locks_id = handler_id; + cc_wwan_device_locks_changed_cb (self); + cc_wwan_device_state_changed_cb (self); + } +} + +static void +cc_wwan_device_signal_quality_changed_cb (CcWwanDevice *self) +{ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SIGNAL]); +} + +static void +cc_wwan_device_mode_changed_cb (CcWwanDevice *self) +{ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NETWORK_MODE]); +} + +static void +wwan_device_emit_data_changed (CcWwanDevice *self) +{ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_DATA]); +} + +static void +cc_wwan_device_unlock_required_cb (CcWwanDevice *self) +{ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UNLOCK_REQUIRED]); +} + +#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) +static void +cc_wwan_device_nm_changed_cb (CcWwanDevice *self, + GParamSpec *pspec, + NMClient *client) +{ + gboolean nm_is_running; + + nm_is_running = nm_client_get_nm_running (client); + + if (!nm_is_running && self->wwan_data != NULL) + { + g_clear_object (&self->wwan_data); + wwan_device_emit_data_changed (self); + } +} + +static void +cc_wwan_device_nm_device_added_cb (CcWwanDevice *self, + NMDevice *nm_device) +{ + if (!NM_IS_DEVICE_MODEM (nm_device)) + return; + + if(!self->sim || !cc_wwan_device_is_nm_device (self, G_OBJECT (nm_device))) + return; + + self->wwan_data = cc_wwan_data_new (self->mm_object, + NM_CLIENT (self->nm_client)); + + if (self->wwan_data) + { + g_signal_connect_object (self->wwan_data, "notify::enabled", + G_CALLBACK (wwan_device_emit_data_changed), + self, G_CONNECT_SWAPPED); + wwan_device_emit_data_changed (self); + } +} +#endif + +static void +cc_wwan_device_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcWwanDevice *self = (CcWwanDevice *)object; + MMModemMode allowed, preferred; + + switch (prop_id) + { + case PROP_OPERATOR_NAME: + g_value_set_string (value, cc_wwan_device_get_operator_name (self)); + break; + + case PROP_ERROR: + g_value_set_boolean (value, self->error != NULL); + break; + + case PROP_HAS_DATA: + g_value_set_boolean (value, self->wwan_data != NULL); + break; + + case PROP_ENABLED_LOCKS: + g_value_set_int (value, self->locks); + break; + + case PROP_NETWORK_MODE: + if (cc_wwan_device_get_current_mode (self, &allowed, &preferred)) + g_value_take_string (value, cc_wwan_device_get_string_from_mode (self, allowed, preferred)); + break; + + case PROP_REGISTRATION_STATE: + g_value_set_int (value, self->registration_state); + break; + + case PROP_UNLOCK_REQUIRED: + g_value_set_int (value, cc_wwan_device_get_lock (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_wwan_device_dispose (GObject *object) +{ + CcWwanDevice *self = (CcWwanDevice *)object; + + g_clear_error (&self->error); + g_clear_object (&self->modem); + g_clear_object (&self->mm_object); + g_clear_object (&self->sim); + g_clear_object (&self->modem_3gpp); + + g_clear_object (&self->nm_client); + g_clear_object (&self->wwan_data); + + G_OBJECT_CLASS (cc_wwan_device_parent_class)->dispose (object); +} + +static void +cc_wwan_device_class_init (CcWwanDeviceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = cc_wwan_device_get_property; + object_class->dispose = cc_wwan_device_dispose; + + properties[PROP_OPERATOR_NAME] = + g_param_spec_string ("operator-name", + "Operator Name", + "Operator Name the device is connected to", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_ENABLED_LOCKS] = + g_param_spec_int ("enabled-locks", + "Enabled Locks", + "Locks Enabled in Modem", + MM_MODEM_3GPP_FACILITY_NONE, + MM_MODEM_3GPP_FACILITY_CORP_PERS, + MM_MODEM_3GPP_FACILITY_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_ERROR] = + g_param_spec_boolean ("error", + "Error", + "Set if some Error occurs", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_HAS_DATA] = + g_param_spec_boolean ("has-data", + "has-data", + "Data for the device", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_NETWORK_MODE] = + g_param_spec_string ("network-mode", + "Network Mode", + "A String representing preferred network mode", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_REGISTRATION_STATE] = + g_param_spec_int ("registration-state", + "Registration State", + "The current network registration state", + CC_WWAN_REGISTRATION_STATE_UNKNOWN, + CC_WWAN_REGISTRATION_STATE_DENIED, + CC_WWAN_REGISTRATION_STATE_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_UNLOCK_REQUIRED] = + g_param_spec_int ("unlock-required", + "Unlock Required", + "The Modem lock status changed", + MM_MODEM_LOCK_UNKNOWN, + MM_MODEM_LOCK_PH_NETSUB_PUK, + MM_MODEM_LOCK_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_SIGNAL] = + g_param_spec_int ("signal", + "Signal", + "Get Device Signal", + 0, 100, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +cc_wwan_device_init (CcWwanDevice *self) +{ +} + +/** + * cc_wwan_device_new: + * @mm_object: (transfer full): An #MMObject + * + * Create a new device representing the given + * @mm_object. + * + * Returns: A #CcWwanDevice + */ +CcWwanDevice * +cc_wwan_device_new (MMObject *mm_object, + GObject *nm_client) +{ + CcWwanDevice *self; + + g_return_val_if_fail (MM_IS_OBJECT (mm_object), NULL); +#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) + g_return_val_if_fail (NM_IS_CLIENT (nm_client), NULL); +#else + g_return_val_if_fail (!nm_client, NULL); +#endif + + self = g_object_new (CC_TYPE_WWAN_DEVICE, NULL); + + self->mm_object = g_object_ref (mm_object); + self->modem = mm_object_get_modem (mm_object); + self->sim = mm_modem_get_sim_sync (self->modem, NULL, NULL); + g_set_object (&self->nm_client, nm_client); + if (self->sim) + { + self->operator_code = mm_sim_get_operator_identifier (self->sim); +#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) + self->wwan_data = cc_wwan_data_new (mm_object, + NM_CLIENT (self->nm_client)); +#endif + } + + g_signal_connect_object (self->mm_object, "notify::unlock-required", + G_CALLBACK (cc_wwan_device_unlock_required_cb), + self, G_CONNECT_SWAPPED); + if (self->wwan_data) + g_signal_connect_object (self->wwan_data, "notify::enabled", + G_CALLBACK (wwan_device_emit_data_changed), + self, G_CONNECT_SWAPPED); + +#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) + g_signal_connect_object (self->nm_client, "notify::nm-running" , + G_CALLBACK (cc_wwan_device_nm_changed_cb), self, + G_CONNECT_SWAPPED); + + g_signal_connect_object (self->nm_client, "device-added", + G_CALLBACK (cc_wwan_device_nm_device_added_cb), + self, G_CONNECT_SWAPPED); +#endif + + g_signal_connect_object (self->mm_object, "notify::modem3gpp", + G_CALLBACK (cc_wwan_device_3gpp_changed_cb), + self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->modem, "notify::signal-quality", + G_CALLBACK (cc_wwan_device_signal_quality_changed_cb), + self, G_CONNECT_SWAPPED); + + cc_wwan_device_3gpp_changed_cb (self); + g_signal_connect_object (self->modem, "notify::current-modes", + G_CALLBACK (cc_wwan_device_mode_changed_cb), + self, G_CONNECT_SWAPPED); + + return self; +} + +gboolean +cc_wwan_device_has_sim (CcWwanDevice *self) +{ + MMModemStateFailedReason state_reason; + + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); + + state_reason = mm_modem_get_state_failed_reason (self->modem); + + if (state_reason == MM_MODEM_STATE_FAILED_REASON_SIM_MISSING) + return FALSE; + + return TRUE; +} + +/** + * cc_wwan_device_get_lock: + * @self: a #CcWwanDevice + * + * Get the active device lock that is required to + * be unlocked for accessing device features. + * + * Returns: %TRUE if PIN enabled, %FALSE otherwise. + */ +MMModemLock +cc_wwan_device_get_lock (CcWwanDevice *self) +{ + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), MM_MODEM_LOCK_UNKNOWN); + + return mm_modem_get_unlock_required (self->modem); +} + + +/** + * cc_wwan_device_get_sim_lock: + * @self: a #CcWwanDevice + * + * Get if SIM lock with PIN is enabled. SIM PIN + * enabled doesn’t mean that SIM is locked. + * See cc_wwan_device_get_lock(). + * + * Returns: %TRUE if PIN enabled, %FALSE otherwise. + */ +gboolean +cc_wwan_device_get_sim_lock (CcWwanDevice *self) +{ + gboolean sim_lock; + + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); + + sim_lock = self->locks & MM_MODEM_3GPP_FACILITY_SIM; + + return !!sim_lock; +} + +guint +cc_wwan_device_get_unlock_retries (CcWwanDevice *self, + MMModemLock lock) +{ + MMUnlockRetries *retries; + + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), 0); + + retries = mm_modem_get_unlock_retries (self->modem); + + return mm_unlock_retries_get (retries, lock); +} + +static void +cc_wwan_device_pin_sent_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + CcWwanDevice *self; + MMSim *sim = (MMSim *)object; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + + if (!mm_sim_send_pin_finish (sim, result, &error)) + { + self = g_task_get_source_object (G_TASK (task)); + + g_clear_error (&self->error); + self->error = g_error_copy (error); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); + + g_task_return_error (task, g_steal_pointer (&error)); + } + else + { + g_task_return_boolean (task, TRUE); + } +} + +void +cc_wwan_device_send_pin (CcWwanDevice *self, + const gchar *pin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (CC_IS_WWAN_DEVICE (self)); + g_return_if_fail (MM_IS_SIM (self->sim)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (pin && *pin); + + task = g_task_new (self, cancellable, callback, user_data); + + mm_sim_send_pin (self->sim, pin, cancellable, + cc_wwan_device_pin_sent_cb, + g_steal_pointer (&task)); +} + +gboolean +cc_wwan_device_send_pin_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); + g_return_val_if_fail (G_IS_TASK (result), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +cc_wwan_device_puk_sent_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + CcWwanDevice *self; + MMSim *sim = (MMSim *)object; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + + if (!mm_sim_send_puk_finish (sim, result, &error)) + { + self = g_task_get_source_object (G_TASK (task)); + + g_clear_error (&self->error); + self->error = g_error_copy (error); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); + + g_task_return_error (task, g_steal_pointer (&error)); + } + else + { + g_task_return_boolean (task, TRUE); + } +} + +void +cc_wwan_device_send_puk (CcWwanDevice *self, + const gchar *puk, + const gchar *pin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (CC_IS_WWAN_DEVICE (self)); + g_return_if_fail (MM_IS_SIM (self->sim)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (puk && *puk); + g_return_if_fail (pin && *pin); + + task = g_task_new (self, cancellable, callback, user_data); + + mm_sim_send_puk (self->sim, puk, pin, cancellable, + cc_wwan_device_puk_sent_cb, + g_steal_pointer (&task)); +} + +gboolean +cc_wwan_device_send_puk_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); + g_return_val_if_fail (G_IS_TASK (result), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +cc_wwan_device_enable_pin_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + CcWwanDevice *self; + MMSim *sim = (MMSim *)object; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + + if (!mm_sim_enable_pin_finish (sim, result, &error)) + { + self = g_task_get_source_object (G_TASK (task)); + + g_clear_error (&self->error); + self->error = g_error_copy (error); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); + + g_task_return_error (task, g_steal_pointer (&error)); + } + else + { + g_task_return_boolean (task, TRUE); + } +} + +void +cc_wwan_device_enable_pin (CcWwanDevice *self, + const gchar *pin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (CC_IS_WWAN_DEVICE (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (pin && *pin); + + task = g_task_new (self, cancellable, callback, user_data); + + mm_sim_enable_pin (self->sim, pin, cancellable, + cc_wwan_device_enable_pin_cb, + g_steal_pointer (&task)); +} + +gboolean +cc_wwan_device_enable_pin_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); + g_return_val_if_fail (G_IS_TASK (result), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +cc_wwan_device_disable_pin_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + CcWwanDevice *self; + MMSim *sim = (MMSim *)object; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + + if (!mm_sim_disable_pin_finish (sim, result, &error)) + { + self = g_task_get_source_object (G_TASK (task)); + + g_clear_error (&self->error); + self->error = g_error_copy (error); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); + + g_task_return_error (task, g_steal_pointer (&error)); + } + else + { + g_task_return_boolean (task, TRUE); + } +} + +void +cc_wwan_device_disable_pin (CcWwanDevice *self, + const gchar *pin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (CC_IS_WWAN_DEVICE (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (pin && *pin); + + task = g_task_new (self, cancellable, callback, user_data); + + mm_sim_disable_pin (self->sim, pin, cancellable, + cc_wwan_device_disable_pin_cb, + g_steal_pointer (&task)); +} + +gboolean +cc_wwan_device_disable_pin_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); + g_return_val_if_fail (G_IS_TASK (result), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +cc_wwan_device_change_pin_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + CcWwanDevice *self; + MMSim *sim = (MMSim *)object; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + + if (!mm_sim_change_pin_finish (sim, result, &error)) + { + self = g_task_get_source_object (G_TASK (task)); + + g_clear_error (&self->error); + self->error = g_error_copy (error); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); + + g_task_return_error (task, g_steal_pointer (&error)); + } + else + { + g_task_return_boolean (task, TRUE); + } +} + +void +cc_wwan_device_change_pin (CcWwanDevice *self, + const gchar *old_pin, + const gchar *new_pin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (CC_IS_WWAN_DEVICE (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (old_pin && *old_pin); + g_return_if_fail (new_pin && *new_pin); + + task = g_task_new (self, cancellable, callback, user_data); + + mm_sim_change_pin (self->sim, old_pin, new_pin, cancellable, + cc_wwan_device_change_pin_cb, + g_steal_pointer (&task)); +} + +gboolean +cc_wwan_device_change_pin_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); + g_return_val_if_fail (G_IS_TASK (result), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +cc_wwan_device_network_mode_set_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + CcWwanDevice *self; + MMModem *modem = (MMModem *)object; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + + if (!mm_modem_set_current_modes_finish (modem, result, &error)) + { + self = g_task_get_source_object (G_TASK (task)); + + g_clear_error (&self->error); + self->error = g_error_copy (error); + g_warning ("Error: %s", error->message); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); + + g_task_return_error (task, g_steal_pointer (&error)); + } + else + { + g_task_return_boolean (task, TRUE); + } +} + +/** + * cc_wwan_device_set_network_mode: + * @self: a #CcWwanDevice + * @allowed: The allowed #MMModemModes + * @preferred: The preferred #MMModemMode + * @cancellable: (nullable): a #GCancellable or %NULL + * @callback: (nullable): a #GAsyncReadyCallback or %NULL + * @user_data: (nullable): closure data for @callback + * + * Asynchronously set preferred network mode. + * + * Call @cc_wwan_device_set_current_mode_finish() + * in @callback to get the result of operation. + */ +void +cc_wwan_device_set_current_mode (CcWwanDevice *self, + MMModemMode allowed, + MMModemMode preferred, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + GPermission *permission; + g_autoptr(GError) error = NULL; + + g_return_if_fail (CC_IS_WWAN_DEVICE (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + permission = polkit_permission_new_sync ("org.freedesktop.ModemManager1.Device.Control", + NULL, cancellable, &error); + if (permission) + g_task_set_task_data (task, permission, g_object_unref); + + if (error) + g_warning ("error: %s", error->message); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + } + else if (!g_permission_get_allowed (permission)) + { + error = g_error_new (G_IO_ERROR, + G_IO_ERROR_PERMISSION_DENIED, + "Access Denied"); + g_clear_error (&self->error); + self->error = g_error_copy (error); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); + + g_task_return_error (task, g_steal_pointer (&error)); + } + else + { + mm_modem_set_current_modes (self->modem, allowed, preferred, + cancellable, cc_wwan_device_network_mode_set_cb, + g_steal_pointer (&task)); + } +} + +/** + * cc_wwan_device_set_current_mode_finish: + * @self: a #CcWwanDevice + * @result: a #GAsyncResult + * @error: a location for #GError or %NULL + * + * Get the status whether setting network mode + * succeeded + * + * Returns: %TRUE if network mode was successfully set, + * %FALSE otherwise. + */ +gboolean +cc_wwan_device_set_current_mode_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); + g_return_val_if_fail (G_IS_TASK (result), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +gboolean +cc_wwan_device_get_current_mode (CcWwanDevice *self, + MMModemMode *allowed, + MMModemMode *preferred) +{ + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); + + return mm_modem_get_current_modes (self->modem, allowed, preferred); +} + +gboolean +cc_wwan_device_is_auto_network (CcWwanDevice *self) +{ + /* + * XXX: ModemManager Doesn’t have a true API to check + * if registration is automatic or manual. So Let’s + * do some guess work. + */ + if (self->registration_state == CC_WWAN_REGISTRATION_STATE_DENIED) + return FALSE; + + return !self->network_is_manual; +} + +CcWwanState +cc_wwan_device_get_network_state (CcWwanDevice *self) +{ + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), 0); + + return self->registration_state; +} + +gboolean +cc_wwan_device_get_supported_modes (CcWwanDevice *self, + MMModemMode *allowed, + MMModemMode *preferred) +{ + g_autofree MMModemModeCombination *modes = NULL; + guint n_modes, i; + + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); + + if (!mm_modem_get_supported_modes (self->modem, &modes, &n_modes)) + return FALSE; + + if (allowed) + *allowed = 0; + if (preferred) + *preferred = 0; + + for (i = 0; i < n_modes; i++) + { + if (allowed) + *allowed = *allowed | modes[i].allowed; + if (preferred) + *preferred = *preferred | modes[i].preferred; + } + + return TRUE; +} + +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); + + if (allowed == MM_MODEM_MODE_2G) + return g_strdup (_("2G Only")); + + if (allowed == MM_MODEM_MODE_3G) + return g_strdup (_("3G Only")); + + if (allowed == MM_MODEM_MODE_4G) + return g_strdup (_("4G Only")); + + if (allowed == MM_MODEM_MODE_5G) + return g_strdup (_("5G Only")); + + str = g_string_sized_new (10); + + if (allowed & MM_MODEM_MODE_2G && + allowed & MM_MODEM_MODE_3G && + allowed & MM_MODEM_MODE_4G && + allowed & MM_MODEM_MODE_5G) + { + if (preferred & MM_MODEM_MODE_5G) + g_string_append (str, _("2G, 3G, 4G, 5G (Preferred)")); + else if (preferred & MM_MODEM_MODE_4G) + g_string_append (str, _("2G, 3G, 4G (Preferred), 5G")); + else if (preferred & MM_MODEM_MODE_3G) + g_string_append (str, _("2G, 3G (Preferred), 4G, 5G")); + else if (preferred & MM_MODEM_MODE_2G) + g_string_append (str, _("2G (Preferred), 3G, 4G, 5G")); + else + g_string_append (str, _("2G, 3G, 4G, 5G")); + } + else if (allowed & MM_MODEM_MODE_2G && + allowed & MM_MODEM_MODE_3G && + allowed & MM_MODEM_MODE_4G) + { + if (preferred & MM_MODEM_MODE_4G) + g_string_append (str, _("2G, 3G, 4G (Preferred)")); + else if (preferred & MM_MODEM_MODE_3G) + g_string_append (str, _("2G, 3G (Preferred), 4G")); + else if (preferred & MM_MODEM_MODE_2G) + g_string_append (str, _("2G (Preferred), 3G, 4G")); + else + g_string_append (str, _("2G, 3G, 4G")); + } + else if (allowed & MM_MODEM_MODE_3G && + allowed & MM_MODEM_MODE_4G && + allowed & MM_MODEM_MODE_5G) + { + if (preferred & MM_MODEM_MODE_5G) + g_string_append (str, _("3G, 4G, 5G (Preferred)")); + else if (preferred & MM_MODEM_MODE_4G) + g_string_append (str, _("3G, 4G (Preferred), 5G")); + else if (preferred & MM_MODEM_MODE_2G) + g_string_append (str, _("3G (Preferred), 4G, 5G")); + else + g_string_append (str, _("3G, 4G, 5G")); + } + else if (allowed & MM_MODEM_MODE_2G && + allowed & MM_MODEM_MODE_4G && + allowed & MM_MODEM_MODE_5G) + { + if (preferred & MM_MODEM_MODE_5G) + g_string_append (str, _("2G, 4G, 5G (Preferred)")); + else if (preferred & MM_MODEM_MODE_4G) + g_string_append (str, _("2G, 4G (Preferred), 5G")); + else if (preferred & MM_MODEM_MODE_2G) + g_string_append (str, _("2G (Preferred), 4G, 5G")); + else + g_string_append (str, _("2G, 4G, 5G")); + } + else if (allowed & MM_MODEM_MODE_2G && + allowed & MM_MODEM_MODE_3G && + allowed & MM_MODEM_MODE_5G) + { + if (preferred & MM_MODEM_MODE_5G) + g_string_append (str, _("2G, 3G, 5G (Preferred)")); + else if (preferred & MM_MODEM_MODE_3G) + g_string_append (str, _("2G, 3G (Preferred), 5G")); + else if (preferred & MM_MODEM_MODE_2G) + g_string_append (str, _("2G (Preferred), 3G, 5G")); + else + g_string_append (str, _("2G, 3G, 5G")); + } + else if (allowed & MM_MODEM_MODE_3G && + allowed & MM_MODEM_MODE_4G) + { + if (preferred & MM_MODEM_MODE_4G) + g_string_append (str, _("3G, 4G (Preferred)")); + else if (preferred & MM_MODEM_MODE_3G) + g_string_append (str, _("3G (Preferred), 4G")); + else + g_string_append (str, _("3G, 4G")); + } + else if (allowed & MM_MODEM_MODE_2G && + allowed & MM_MODEM_MODE_4G) + { + if (preferred & MM_MODEM_MODE_4G) + g_string_append (str, _("2G, 4G (Preferred)")); + else if (preferred & MM_MODEM_MODE_2G) + g_string_append (str, _("2G (Preferred), 4G")); + else + g_string_append (str, _("2G, 4G")); + } + else if (allowed & MM_MODEM_MODE_2G && + allowed & MM_MODEM_MODE_3G) + { + if (preferred & MM_MODEM_MODE_3G) + g_string_append (str, _("2G, 3G (Preferred)")); + else if (preferred & MM_MODEM_MODE_2G) + g_string_append (str, _("2G (Preferred), 3G")); + else + g_string_append (str, _("2G, 3G")); + } + else if (allowed & MM_MODEM_MODE_2G && + allowed & MM_MODEM_MODE_5G) + { + if (preferred & MM_MODEM_MODE_5G) + g_string_append (str, _("2G, 5G (Preferred)")); + else if (preferred & MM_MODEM_MODE_2G) + g_string_append (str, _("2G (Preferred), 5G")); + else + g_string_append (str, _("2G, 5G")); + } + else if (allowed & MM_MODEM_MODE_3G && + allowed & MM_MODEM_MODE_5G) + { + if (preferred & MM_MODEM_MODE_5G) + g_string_append (str, _("3G, 5G (Preferred)")); + else if (preferred & MM_MODEM_MODE_3G) + g_string_append (str, _("3G (Preferred), 5G")); + else + g_string_append (str, _("3G, 5G")); + } + else if (allowed & MM_MODEM_MODE_4G && + allowed & MM_MODEM_MODE_5G) + { + if (preferred & MM_MODEM_MODE_5G) + g_string_append (str, _("4G, 5G (Preferred)")); + else if (preferred & MM_MODEM_MODE_4G) + g_string_append (str, _("4G (Preferred), 5G")); + else + g_string_append (str, _("4G, 5G")); + } + + if (!str->len) + g_string_append (str, C_("Network mode", "Unknown")); + + return g_string_free (str, FALSE); +} + +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; + 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) + 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 (""); + + /* 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; + + g_dbus_error_strip_remote_error (self->error); + + 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 0000000..e484bcf --- /dev/null +++ b/panels/wwan/cc-wwan-device.h @@ -0,0 +1,152 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-device.h + * + * Copyright 2019-2020 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <glib-object.h> +#include <libmm-glib.h> + +#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) +# include "cc-wwan-data.h" +#endif + +G_BEGIN_DECLS + +typedef enum +{ + CC_WWAN_REGISTRATION_STATE_UNKNOWN, + CC_WWAN_REGISTRATION_STATE_IDLE, + CC_WWAN_REGISTRATION_STATE_REGISTERED, + CC_WWAN_REGISTRATION_STATE_ROAMING, + CC_WWAN_REGISTRATION_STATE_SEARCHING, + CC_WWAN_REGISTRATION_STATE_DENIED +} CcWwanState; + +typedef struct _CcWwanData CcWwanData; + +#define CC_TYPE_WWAN_DEVICE (cc_wwan_device_get_type()) +G_DECLARE_FINAL_TYPE (CcWwanDevice, cc_wwan_device, CC, WWAN_DEVICE, GObject) + +CcWwanDevice *cc_wwan_device_new (MMObject *mm_object, + GObject *nm_client); +gboolean cc_wwan_device_has_sim (CcWwanDevice *self); +MMModemLock cc_wwan_device_get_lock (CcWwanDevice *self); +gboolean cc_wwan_device_get_sim_lock (CcWwanDevice *self); +guint cc_wwan_device_get_unlock_retries (CcWwanDevice *self, + MMModemLock lock); +void cc_wwan_device_enable_pin (CcWwanDevice *self, + const gchar *pin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean cc_wwan_device_enable_pin_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error); +void cc_wwan_device_disable_pin (CcWwanDevice *self, + const gchar *pin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean cc_wwan_device_disable_pin_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error); +void cc_wwan_device_send_pin (CcWwanDevice *self, + const gchar *pin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean cc_wwan_device_send_pin_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error); +void cc_wwan_device_send_puk (CcWwanDevice *self, + const gchar *puk, + const gchar *pin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean cc_wwan_device_send_puk_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error); +void cc_wwan_device_change_pin (CcWwanDevice *self, + const gchar *old_pin, + const gchar *new_pin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean cc_wwan_device_change_pin_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error); +const gchar *cc_wwan_device_get_operator_name (CcWwanDevice *self); +gchar *cc_wwan_device_dup_own_numbers (CcWwanDevice *self); +gchar *cc_wwan_device_dup_network_type_string (CcWwanDevice *self); +gchar *cc_wwan_device_dup_signal_string (CcWwanDevice *self); +const gchar *cc_wwan_device_get_manufacturer (CcWwanDevice *self); +const gchar *cc_wwan_device_get_model (CcWwanDevice *self); +const gchar *cc_wwan_device_get_firmware_version (CcWwanDevice *self); +const gchar *cc_wwan_device_get_identifier (CcWwanDevice *self); +gboolean cc_wwan_device_get_current_mode (CcWwanDevice *self, + MMModemMode *allowed, + MMModemMode *preferred); +gboolean cc_wwan_device_is_auto_network (CcWwanDevice *self); +CcWwanState cc_wwan_device_get_network_state (CcWwanDevice *self); +gboolean cc_wwan_device_get_supported_modes (CcWwanDevice *self, + MMModemMode *allowed, + MMModemMode *preferred); +void cc_wwan_device_set_current_mode (CcWwanDevice *self, + MMModemMode allowed, + MMModemMode preferred, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean cc_wwan_device_set_current_mode_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error); +gchar *cc_wwan_device_get_string_from_mode (CcWwanDevice *self, + MMModemMode allowed, + MMModemMode preferred); +void cc_wwan_device_scan_networks (CcWwanDevice *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GList *cc_wwan_device_scan_networks_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error); +void cc_wwan_device_register_network (CcWwanDevice *self, + const gchar *network_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean cc_wwan_device_register_network_finish (CcWwanDevice *self, + GAsyncResult *result, + GError **error); +const gchar *cc_wwan_device_get_simple_error (CcWwanDevice *self); +GSList *cc_wwan_device_get_apn_list (CcWwanDevice *self); +gboolean cc_wwan_device_is_nm_device (CcWwanDevice *self, + GObject *nm_device); +const gchar *cc_wwan_device_get_path (CcWwanDevice *self); +CcWwanData *cc_wwan_device_get_data (CcWwanDevice *self); +gboolean cc_wwan_device_pin_valid (const gchar *password, + MMModemLock lock); + +G_END_DECLS diff --git a/panels/wwan/cc-wwan-errors-private.h b/panels/wwan/cc-wwan-errors-private.h new file mode 100644 index 0000000..15cc76a --- /dev/null +++ b/panels/wwan/cc-wwan-errors-private.h @@ -0,0 +1,107 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-errors-private.h + * + * Copyright 2019 Purism SPC + * + * Modified from mm-error-helpers.c from ModemManager + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <glib/gi18n.h> +#include <glib-object.h> +#include <libmm-glib.h> + +typedef struct { + guint code; + const gchar *message; +} ErrorTable; + + +static ErrorTable me_errors[] = { + { MM_MOBILE_EQUIPMENT_ERROR_PHONE_FAILURE, N_("Phone failure") }, + { MM_MOBILE_EQUIPMENT_ERROR_NO_CONNECTION, N_("No connection to phone") }, + { MM_MOBILE_EQUIPMENT_ERROR_LINK_RESERVED, "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, "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") }, + { 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, "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, "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, "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, "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, "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, "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, "PDP authentication failure" }, + { MM_MOBILE_EQUIPMENT_ERROR_GPRS_INVALID_MOBILE_CLASS, "Invalid mobile class" }, +}; + +static inline const gchar * +cc_wwan_error_get_message (GError *error) +{ + if (!error) + return _("No 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 0000000..dd5a4a1 --- /dev/null +++ b/panels/wwan/cc-wwan-mode-dialog.c @@ -0,0 +1,335 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-mode-dialog.c + * + * Copyright 2019,2022 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-network-mode-dialog" + +#include <config.h> +#include <glib/gi18n.h> +#include <libmm-glib.h> + +#include "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-top", 18, + "margin-bottom", 18, + "margin-start", 18, + "margin-end", 18, + NULL); + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box); + + mode = cc_wwan_device_get_string_from_mode (self->device, allowed, preferred); + label = gtk_label_new (mode); + gtk_widget_set_hexpand (label, TRUE); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_box_append (GTK_BOX (box), label); + + /* image should be hidden by default */ + image = gtk_image_new_from_icon_name ("emblem-ok-symbolic"); + gtk_widget_hide (image); + row->ok_emblem = GTK_IMAGE (image); + gtk_box_append (GTK_BOX (box), 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_5G, MM_MODEM_MODE_5G}, + {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, 0}, + {MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, MM_MODEM_MODE_5G}, + {MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, 0}, + {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_5G, 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_list_box_append (GTK_LIST_BOX (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; + } + + self->selected_row = NULL; + + for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->network_mode_list)); + child; + child = gtk_widget_get_next_sibling (child)) + cc_wwan_mode_dialog_update_mode (CC_WWAN_MODE_ROW (child), 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)); +} + +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 0000000..0916ddf --- /dev/null +++ b/panels/wwan/cc-wwan-mode-dialog.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-mode-dialog.h + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <adwaita.h> +#include <shell/cc-panel.h> + +#include "cc-wwan-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WWAN_MODE_DIALOG (cc_wwan_mode_dialog_get_type()) +G_DECLARE_FINAL_TYPE (CcWwanModeDialog, cc_wwan_mode_dialog, CC, WWAN_MODE_DIALOG, GtkDialog) + +CcWwanModeDialog *cc_wwan_mode_dialog_new (GtkWindow *parent_window, + CcWwanDevice *device); + +G_END_DECLS diff --git a/panels/wwan/cc-wwan-mode-dialog.ui b/panels/wwan/cc-wwan-mode-dialog.ui new file mode 100644 index 0000000..4c350c8 --- /dev/null +++ b/panels/wwan/cc-wwan-mode-dialog.ui @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk" version="4.0"/> + <template class="CcWwanModeDialog" parent="GtkDialog"> + <property name="title" translatable="yes">Network Mode</property> + <property name="default-height">480</property> + <property name="default-width">360</property> + <property name="hide-on-close">True</property> + <property name="modal">True</property> + + <child> + <object class="AdwPreferencesPage"> + <child> + <object class="AdwPreferencesGroup"> + <child> + <object class="GtkListBox" id="network_mode_list"> + <property name="selection-mode">none</property> + <signal name="row-activated" handler="cc_wwan_mode_changed_cb" swapped="yes"/> + <style> + <class name="boxed-list"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + + <child type="action"> + <object class="GtkButton" id="button_cancel"> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Cancel</property> + <signal name="clicked" handler="gtk_widget_hide" swapped="yes"/> + </object> + </child> + <child type="action"> + <object class="GtkButton" id="button_ok"> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Set</property> + <signal name="clicked" handler="cc_wwan_mode_dialog_ok_clicked_cb" swapped="yes"/> + <style> + <class name="suggested-action "/> + </style> + </object> + </child> + + <action-widgets> + <action-widget response="cancel">button_cancel</action-widget> + <action-widget response="apply" default="True">button_ok</action-widget> + </action-widgets> + </template> +</interface> diff --git a/panels/wwan/cc-wwan-network-dialog.c b/panels/wwan/cc-wwan-network-dialog.c new file mode 100644 index 0000000..ac00547 --- /dev/null +++ b/panels/wwan/cc-wwan-network-dialog.c @@ -0,0 +1,432 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-network-dialog.c + * + * Copyright 2019,2022 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-wwan-network-dialog" + +#include <config.h> +#include <glib/gi18n.h> +#include <libmm-glib.h> + +#include "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; + + AdwToastOverlay *toast_overlay; + CcListRow *automatic_row; + GtkButton *button_apply; + GtkSpinner *loading_spinner; + GtkBox *network_search_title; + GtkListBox *operator_list_box; + GtkButton *refresh_button; + + CcWwanDevice *device; + GList *operator_list; + + CcWwanNetworkRow *selected_row; + + GCancellable *search_cancellable; + + 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_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-top", 18, + "margin-bottom", 18, + "margin-start", 18, + "margin-end", 18, + NULL); + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box); + + label = gtk_label_new (operator_name); + gtk_widget_set_hexpand (label, TRUE); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_box_append (GTK_BOX (box), label); + + image = gtk_image_new_from_icon_name ("emblem-ok-symbolic"); + gtk_widget_hide (image); + row->ok_emblem = GTK_IMAGE (image); + gtk_box_append (GTK_BOX (box), image); + + row->operator_code = g_strdup (operator_code); + + return row; +} + +static void +cc_wwan_network_dialog_update_current_network (CcWwanNetworkDialog *self) +{ + CcWwanNetworkRow *row; + GtkWidget *child; + const gchar *operator_name; + + operator_name = cc_wwan_device_get_operator_name (self->device); + + if (!operator_name || operator_name[0] == '\0') + return; + + child = gtk_widget_get_first_child (GTK_WIDGET (self->operator_list_box)); + + while (child) + { + GtkWidget *next; + + next = gtk_widget_get_next_sibling (child); + gtk_list_box_remove (GTK_LIST_BOX (self->operator_list_box), child); + + child = next; + } + + row = cc_wwan_network_dialog_row_new (self, operator_name, ""); + self->selected_row = row; + gtk_widget_show (GTK_WIDGET (row->ok_emblem)); + gtk_list_box_append (GTK_LIST_BOX (self->operator_list_box), GTK_WIDGET (row)); +} + +static void +cc_wwan_network_dialog_update (CcWwanNetworkDialog *self) +{ + CcWwanNetworkRow *row; + GtkWidget *child; + GList *item; + const gchar *operator_code, *operator_name; + + child = gtk_widget_get_first_child (GTK_WIDGET (self->operator_list_box)); + + while (child) + { + GtkWidget *next; + + next = gtk_widget_get_next_sibling (child); + gtk_list_box_remove (GTK_LIST_BOX (self->operator_list_box), child); + + child = next; + } + + 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_list_box_append (GTK_LIST_BOX (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)) + { + AdwToast *toast; + + self->no_update_network = TRUE; + gtk_widget_activate (GTK_WIDGET (self->automatic_row)); + gtk_widget_set_sensitive (GTK_WIDGET (self->operator_list_box), FALSE); + + toast = adw_toast_new (cc_wwan_error_get_message (error)); + adw_toast_overlay_add_toast (self->toast_overlay, toast); + + 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); + + self->no_update_network = TRUE; + 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; + + 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, toast_overlay); + 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, 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_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 (); +} + +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 0000000..bd7091e --- /dev/null +++ b/panels/wwan/cc-wwan-network-dialog.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-network-dialog.h + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <adwaita.h> +#include <shell/cc-panel.h> + +#include "cc-wwan-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WWAN_NETWORK_DIALOG (cc_wwan_network_dialog_get_type()) +G_DECLARE_FINAL_TYPE (CcWwanNetworkDialog, cc_wwan_network_dialog, CC, WWAN_NETWORK_DIALOG, GtkDialog) + +CcWwanNetworkDialog *cc_wwan_network_dialog_new (GtkWindow *parent_window, + CcWwanDevice *device); + +G_END_DECLS diff --git a/panels/wwan/cc-wwan-network-dialog.ui b/panels/wwan/cc-wwan-network-dialog.ui new file mode 100644 index 0000000..1ffc4e5 --- /dev/null +++ b/panels/wwan/cc-wwan-network-dialog.ui @@ -0,0 +1,128 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk" version="4.0"/> + <template class="CcWwanNetworkDialog" parent="GtkDialog"> + <property name="title" translatable="yes">Network</property> + <property name="default-height">480</property> + <property name="default-width">360</property> + <property name="hide-on-close">True</property> + <property name="modal">True</property> + + <child> + <object class="GtkBox"> + <property name="width-request">340</property> + <property name="height-request">360</property> + <child> + <object class="AdwToastOverlay" id="toast_overlay"> + <property name="hexpand">True</property> + <property name="child"> + <object class="GtkBox"> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-top">18</property> + <property name="margin-bottom">18</property> + <property name="orientation">vertical</property> + + <!-- Automatic Network Selection Switch --> + <child> + <object class="GtkListBox"> + <property name="selection-mode">none</property> + <property name="margin-bottom">18</property> + <style> + <class name="boxed-list"/> + </style> + <child> + <object class="CcListRow" id="automatic_row"> + <property name="show-switch">True</property> + <property name="use-underline">True</property> + <property name="title" translatable="yes">_Automatic</property> + <signal name="notify::active" handler="cc_wwan_auto_network_changed_cb" swapped="yes"/> + </object> + </child> + </object> + </child> + + <!-- Network Selection List Title and Spinner --> + <child> + <object class="GtkBox" id="network_search_title"> + <property name="visible" bind-source="automatic_row" bind-property="active" bind-flags="sync-create|invert-boolean"/> + <property name="margin-bottom">9</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Choose Network</property> + <property name="xalign">0.0</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkSpinner" id="loading_spinner"/> + </child> + <child> + <object class="GtkButton" id="refresh_button"> + <property name="icon-name">view-refresh-symbolic</property> + <signal name="clicked" handler="cc_wwan_network_dialog_refresh_networks" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Refresh Network Providers</property> + </accessibility> + </object> + </child> + </object> + </child> + + <!-- Network Selection List --> + <child> + <object class="GtkScrolledWindow"> + <property name="hscrollbar-policy">never</property> + <property name="propagate-natural-height">True</property> + <property name="visible" bind-source="operator_list_box" bind-flags="sync-create"/> + <style> + <class name="frame"/> + </style> + <property name="child"> + <object class="GtkListBox" id="operator_list_box"> + <property name="visible">False</property> + <property name="sensitive">False</property> + <property name="selection-mode">none</property> + <signal name="row-activated" handler="cc_wwan_network_changed_cb" swapped="yes"/> + <style> + <class name="boxed-list"/> + </style> + </object> + </property> + </object> + </child> + + </object> + </property> + </object> + </child> + </object> + </child> + + <child type="action"> + <object class="GtkButton" id="button_cancel"> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Cancel</property> + <signal name="clicked" handler="gtk_widget_hide" swapped="yes"/> + </object> + </child> + <child type="action"> + <object class="GtkButton" id="button_apply"> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Set</property> + <signal name="clicked" handler="cc_wwan_network_dialog_apply_clicked_cb" swapped="yes"/> + <style> + <class name="suggested-action "/> + </style> + </object> + </child> + + <action-widgets> + <action-widget response="cancel">button_cancel</action-widget> + <action-widget response="apply" default="True">button_apply</action-widget> + </action-widgets> + </template> +</interface> diff --git a/panels/wwan/cc-wwan-panel.c b/panels/wwan/cc-wwan-panel.c new file mode 100644 index 0000000..066a615 --- /dev/null +++ b/panels/wwan/cc-wwan-panel.c @@ -0,0 +1,836 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-panel.c + * + * Copyright 2019,2022 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-wwan-panel" + +#include <config.h> +#include <glib/gi18n.h> +#include <libmm-glib.h> + +#include "cc-wwan-device.h" +#include "cc-wwan-data.h" +#include "cc-wwan-device-page.h" +#include "cc-wwan-panel.h" +#include "cc-wwan-resources.h" + +#include "shell/cc-application.h" +#include "shell/cc-debug.h" +#include "shell/cc-object-storage.h" + +typedef enum { + OPERATION_NULL, + OPERATION_SHOW_DEVICE, +} CmdlineOperation; + +struct _CcWwanPanel +{ + CcPanel parent_instance; + + AdwToastOverlay *toast_overlay; + AdwComboRow *data_list_row; + GtkListBox *data_sim_select_listbox; + GtkStack *devices_stack; + GtkStackSwitcher *devices_switcher; + GtkSwitch *enable_switch; + GtkStack *main_stack; + GtkRevealer *multi_device_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; + GListStore *data_devices_name_list; + GCancellable *cancellable; + + CmdlineOperation arg_operation; + char *arg_device; +}; + +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) + { + for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->devices_stack)); + child; + child = gtk_widget_get_next_sibling (child)) + { + CcWwanDevice *device; + + device = cc_wwan_device_page_get_device (CC_WWAN_DEVICE_PAGE (child)); + + if (g_strcmp0 (cc_wwan_device_get_path (device), self->arg_device) == 0) + { + gtk_stack_set_visible_child (GTK_STACK (self->devices_stack), child); + 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 void +cc_wwan_panel_update_data_selection (CcWwanPanel *self) +{ + int i; + + if (!self->data_device) + return; + + i = wwan_model_get_item_index (G_LIST_MODEL (self->data_devices), self->data_device); + + if (i != -1) + adw_combo_row_set_selected (self->data_list_row, i); +} + +static void +cc_wwan_data_item_activate_cb (CcWwanPanel *self, + CcWwanDevice *device) +{ + CcWwanData *data; + + if (device == self->data_device) + return; + + if (!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 (device); + cc_wwan_data_set_priority (data, CC_WWAN_APN_PRIORITY_HIGH); + cc_wwan_data_save_settings (data, NULL, NULL, NULL); + + self->data_device = device; + 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 void +wwan_data_list_selected_sim_changed_cb (CcWwanPanel *self) +{ + CcWwanDevice *device; + GObject *selected; + + g_assert (CC_IS_WWAN_PANEL (self)); + + selected = adw_combo_row_get_selected_item (self->data_list_row); + if (!selected) + return; + + device = g_object_get_data (selected, "device"); + cc_wwan_data_item_activate_cb (self, device); +} + +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_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->toast_overlay)); + 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; + GtkStackPage *page; + gint index; + + g_assert (CC_IS_WWAN_DEVICE_PAGE (device_page)); + + device = cc_wwan_device_page_get_device (device_page); + + page = gtk_stack_get_page (GTK_STACK (self->devices_stack), 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_stack_page_set_title (page, title); + gtk_stack_page_set_name (page, name); +} + +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); + g_list_store_remove (self->data_devices_name_list, 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_stack_remove (GTK_STACK (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); + + for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->devices_stack)); + child; + child = gtk_widget_get_next_sibling (child)) + cc_wwan_panel_update_page_title (CC_WWAN_DEVICE_PAGE (child), self); +} + +static void +wwan_panel_add_data_device_to_list (CcWwanPanel *self, + CcWwanDevice *device) +{ + g_autoptr(GtkStringObject) str = NULL; + g_autofree char *operator = NULL; + int index; + + index = wwan_model_get_item_index (G_LIST_MODEL (self->data_devices), device); + if (index != -1) + return; + + g_list_store_append (self->data_devices, device); + + index = wwan_model_get_item_index (G_LIST_MODEL (self->devices), device); + operator = g_strdup_printf ("SIM %d", index + 1); + str = gtk_string_object_new (operator); + g_object_set_data_full (G_OBJECT (str), "device", g_object_ref (device), g_object_unref); + g_list_store_append (self->data_devices_name_list, str); +} + +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); + g_list_store_remove_all (self->data_devices_name_list); + 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)) + wwan_panel_add_data_device_to_list (self, device); + } + + if (active_data) + cc_wwan_panel_update_data_selection (self); +} + +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)) + wwan_panel_add_data_device_to_list (self, 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_dispose (GObject *object) +{ + CcWwanPanel *self = (CcWwanPanel *)object; + + g_cancellable_cancel (self->cancellable); + + g_clear_object (&self->devices); + g_clear_object (&self->data_devices); + g_clear_object (&self->data_devices_name_list); + 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->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, toast_overlay); + gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_list_row); + 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_callback (widget_class, wwan_data_list_selected_sim_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, wwan_on_airplane_off_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); + self->data_devices_name_list = g_list_store_new (GTK_TYPE_STRING_OBJECT); + adw_combo_row_set_model (ADW_COMBO_ROW (self->data_list_row), + G_LIST_MODEL (self->data_devices_name_list)); + + 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 (self->nm_client) + { + g_object_bind_property (self->nm_client, "wwan-enabled", + self->enable_switch, "active", + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + } + + 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 0000000..57d2dae --- /dev/null +++ b/panels/wwan/cc-wwan-panel.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-panel.h + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define CC_TYPE_WWAN_PANEL (cc_wwan_panel_get_type()) +G_DECLARE_FINAL_TYPE (CcWwanPanel, cc_wwan_panel, CC, WWAN_PANEL, CcPanel) + +void cc_wwan_panel_static_init_func (void); + +G_END_DECLS diff --git a/panels/wwan/cc-wwan-panel.ui b/panels/wwan/cc-wwan-panel.ui new file mode 100644 index 0000000..a984022 --- /dev/null +++ b/panels/wwan/cc-wwan-panel.ui @@ -0,0 +1,198 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk" version="4.0"/> + <template class="CcWwanPanel" parent="CcPanel"> + + <!-- Cellular panel on/off switch --> + <child type="titlebar-end"> + <object class="GtkSwitch" id="enable_switch"> + <accessibility> + <property name="label" translatable="yes">Enable Mobile Network</property> + </accessibility> + </object> + </child> + + <child type="content"> + <object class="AdwToastOverlay" id="toast_overlay"> + <property name="child"> + <object class="GtkScrolledWindow"> + <property name="hscrollbar-policy">never</property> + <property name="min-content-height">500</property> + <child> + <object class="AdwClamp"> + <property name="margin-top">0</property> + <property name="margin-bottom">32</property> + <property name="margin-start">18</property> + <property name="margin-end">18</property> + + <child> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkStack" id="main_stack"> + <property name="vhomogeneous">False</property> + <property name="hhomogeneous">False</property> + <property name="transition-type">crossfade</property> + + <!-- "No WWAN Adapter" page --> + <child> + <object class="GtkStackPage"> + <property name="name">no-wwan-devices</property> + <property name="child"> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkImage"> + <property name="icon-name">network-cellular-offline-symbolic</property> + <property name="pixel-size">192</property> + <property name="margin-bottom">18</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="wrap">True</property> + <property name="label" translatable="yes">No WWAN Adapter Found</property> + <attributes> + <attribute name="weight" value="bold"></attribute> + <attribute name="scale" value="1.2"></attribute> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="wrap">True</property> + <property name="label" translatable="yes">Make sure you have a Wireless Wan/Cellular device</property> + </object> + </child> + </object> + </property> + </object> + </child> + + <!-- "Airplane Mode" page --> + <child> + <object class="GtkStackPage"> + <property name="name">airplane-mode</property> + <property name="child"> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="valign">center</property> + <child> + <object class="GtkImage"> + <property name="icon-name">airplane-mode-symbolic</property> + <property name="pixel-size">192</property> + <property name="margin-bottom">18</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="wrap">True</property> + <property name="label" translatable="yes">Airplane Mode On</property> + <attributes> + <attribute name="weight" value="bold"></attribute> + <attribute name="scale" value="1.2"></attribute> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="wrap">True</property> + <property name="label" translatable="yes">Wireless Wan is disabled when airplane mode is on</property> + </object> + </child> + <child> + <object class="GtkButton"> + <property name="halign">center</property> + <property name="use-underline">True</property> + <property name="margin-top">24</property> + <property name="label" translatable="yes">_Turn off Airplane Mode</property> + <signal name="clicked" handler="wwan_on_airplane_off_clicked_cb" swapped="yes"/> + </object> + </child> + </object> + </property> + </object> + </child> + <child> + <object class="GtkStackPage"> + <property name="name">device-settings</property> + <property name="child"> + <object class="GtkBox"> + <property name="orientation">vertical</property> + + <child> + <object class="GtkRevealer" id="multi_device_revealer"> + <property name="margin-top">18</property> + <property name="child"> + <object class="GtkBox"> + <property name="orientation">vertical</property> + + <!-- Data SIM selector --> + <child> + <object class="GtkListBox" id="data_sim_select_listbox"> + <property name="margin-bottom">32</property> + <style> + <class name="boxed-list"/> + </style> + <child> + <object class="AdwComboRow" id="data_list_row"> + <property name="title" translatable="yes">Data Connection</property> + <property name="subtitle" translatable="yes">SIM card used for internet</property> + <signal name="notify::selected-item" handler="wwan_data_list_selected_sim_changed_cb" swapped="1"/> + </object> + </child> + </object> + </child> + + <!-- Device (SIM) Name --> + <child> + <object class="GtkStackSwitcher" id="devices_switcher"> + <property name="stack">devices_stack</property> + <property name="hexpand">True</property> + <property name="halign">center</property> + </object> + </child> + + </object> + </property> + </object> + </child> + + <!-- Device (SIM) settings page --> + <child> + <object class="GtkStack" id="devices_stack"> + <property name="vhomogeneous">False</property> + <property name="hhomogeneous">False</property> + </object> + </child> + + </object> + </property> + </object> + </child> + </object> <!-- ./GtkStack main_stack --> + </child> + </object> + </child> + + </object> <!-- ./AdwClamp --> + </child> + </object> <!-- ./GtkScrolledWindow --> + </property> + </object> + </child> + + </template> +</interface> diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.c b/panels/wwan/cc-wwan-sim-lock-dialog.c new file mode 100644 index 0000000..09c6773 --- /dev/null +++ b/panels/wwan/cc-wwan-sim-lock-dialog.c @@ -0,0 +1,309 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-network-dialog.c + * + * Copyright 2019,2022 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-wwan-sim-lock-dialog" + +#include <config.h> +#include <glib/gi18n.h> +#include <libmm-glib.h> + +#include "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_editable_set_text (GTK_EDITABLE (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_editable_get_text (GTK_EDITABLE (self->pin_entry)); + new_pin = gtk_editable_get_text (GTK_EDITABLE (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_editable_get_text (GTK_EDITABLE (self->new_pin_entry)); + confirm_pin = gtk_editable_get_text (GTK_EDITABLE (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_editable_get_text (GTK_EDITABLE (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_editable_set_text (GTK_EDITABLE (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_editable_set_text (GTK_EDITABLE (self->pin_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (self->new_pin_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (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 0000000..f59d048 --- /dev/null +++ b/panels/wwan/cc-wwan-sim-lock-dialog.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wwan-sim-lock-dialog.h + * + * Copyright 2019 Purism SPC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <adwaita.h> +#include <shell/cc-panel.h> + +#include "cc-wwan-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WWAN_SIM_LOCK_DIALOG (cc_wwan_sim_lock_dialog_get_type()) +G_DECLARE_FINAL_TYPE (CcWwanSimLockDialog, cc_wwan_sim_lock_dialog, CC, WWAN_SIM_LOCK_DIALOG, GtkDialog) + +CcWwanSimLockDialog *cc_wwan_sim_lock_dialog_new (GtkWindow *parent_window, + CcWwanDevice *device); + +G_END_DECLS diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.ui b/panels/wwan/cc-wwan-sim-lock-dialog.ui new file mode 100644 index 0000000..5c7f736 --- /dev/null +++ b/panels/wwan/cc-wwan-sim-lock-dialog.ui @@ -0,0 +1,249 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk" version="4.0"/> + <template class="CcWwanSimLockDialog" parent="GtkDialog"> + <property name="default-height">480</property> + <property name="default-width">360</property> + <property name="hide-on-close">True</property> + <property name="modal">True</property> + <property name="title" translatable="yes">SIM Lock</property> + + <child type="titlebar"> + <object class="GtkHeaderBar"> + <child type="end"> + <object class="GtkStack" id="button_stack"> + + <child> + <object class="GtkStackPage"> + <property name="name">next</property> + <!-- Next Button --> + <property name="child"> + <object class="GtkButton" id="next_button"> + <property name="sensitive">False</property> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Next</property> + <signal name="clicked" handler="cc_wwan_pin_next_clicked_cb" swapped="yes"/> + <style> + <class name="suggested-action"/> + </style> + </object> + </property> + </object> + </child> + + <child> + <object class="GtkStackPage"> + <property name="name">apply</property> + <!-- Apply button --> + <property name="child"> + <object class="GtkButton" id="apply_button"> + <property name="use-underline">True</property> + <signal name="clicked" handler="cc_wwan_pin_apply_clicked_cb" swapped="yes"/> + <style> + <class name="suggested-action"/> + </style> + </object> + </property> + </object> + </child> + + </object> + </child> + </object> + </child> + + <child> + <object class="GtkBox"> + <property name="width-request">340</property> + <property name="height-request">360</property> + + <child> + <object class="AdwClamp"> + <property name="margin-top">32</property> + <property name="margin-bottom">32</property> + <property name="margin-start">18</property> + <property name="margin-end">18</property> + <child> + <object class="AdwToastOverlay" id="toast_overlay"> + <property name="child"> + <object class="GtkStack" id="pin_settings_stack"> + <property name="transition-type">slide-left</property> + + <child> + <object class="GtkStackPage"> + <property name="name">pin-settings</property> + <property name="child"> + <object class="GtkBox"> + <property name="orientation">vertical</property> + + <!-- SIM Lock Switch --> + <child> + <object class="GtkListBox"> + <property name="selection-mode">none</property> + <property name="margin-bottom">18</property> + <style> + <class name="frame"/> + </style> + <child> + <object class="CcListRow" id="lock_row"> + <property name="show-switch">True</property> + <property name="use-underline">True</property> + <property name="title" translatable="yes">_Lock SIM with PIN</property> + <signal name="notify::active" handler="cc_wwan_sim_lock_changed_cb" swapped="yes"/> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkGrid" id="lock_change_grid"> + <property name="visible">False</property> + <property name="row-spacing">18</property> + <property name="column-spacing">12</property> + + <!-- SIM Lock Change Title --> + <child> + <object class="GtkLabel" id="lock_change_title"> + <property name="label" translatable="yes">Change PIN</property> + <property name="margin-bottom">9</property> + <property name="xalign">0.0</property> + <attributes> + <attribute name="weight" value="bold"></attribute> + </attributes> + <layout> + <property name="column">0</property> + <property name="row">0</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + + <!-- PIN Entry --> + <child> + <object class="GtkLabel"> + <property name="label">New PIN</property> + <property name="halign">end</property> + <property name="mnemonic_widget">new_pin_entry</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="new_pin_entry"> + <property name="visibility">False</property> + <property name="input-purpose">password</property> + <property name="input-hints">no-emoji</property> + <property name="max-length">8</property> + <property name="max-width-chars">32</property> + <signal name="notify::text" handler="cc_wwan_pin_entry_changed_cb" swapped="yes"/> + <signal name="insert-text" handler="cc_wwan_pin_entry_text_inserted_cb" swapped="yes"/> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="label">Confirm</property> + <property name="halign">end</property> + <property name="mnemonic_widget">pin_confirm_entry</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + + <!-- Confirm PIN Entry --> + <child> + <object class="GtkEntry" id="pin_confirm_entry"> + <property name="visibility">False</property> + <property name="input-purpose">password</property> + <property name="input-hints">no-emoji</property> + <property name="max-length">8</property> + <property name="max-width-chars">32</property> + <signal name="notify::text" handler="cc_wwan_pin_entry_changed_cb" swapped="yes"/> + <signal name="insert-text" handler="cc_wwan_pin_entry_changed_cb" swapped="yes"/> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + </object> + </child> + + </object> + </property> + </object> + </child> + + <child> + <object class="GtkStackPage"> + <property name="name">pin-entry</property> + <property name="child"> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="valign">center</property> + <property name="halign">center</property> + <child> + <object class="GtkImage"> + <property name="pixel-size">128</property> + <property name="icon-name">dialog-password-symbolic</property> + </object> + </child> + <child> + <object class="GtkLabel" id=""> + <property name="label" translatable="yes">Enter current PIN to change SIM lock settings</property> + <property name="margin-bottom">24</property> + <property name="halign">center</property> + <property name="mnemonic_widget">pin_entry</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkEntry" id="pin_entry"> + <property name="visibility">False</property> + <property name="input-purpose">password</property> + <property name="input-hints">no-emoji</property> + <property name="max-length">8</property> + <property name="max-width-chars">32</property> + <signal name="notify::text" handler="cc_wwan_pin_entered_cb" swapped="yes"/> + <signal name="insert-text" handler="cc_wwan_pin_entry_text_inserted_cb" swapped="yes"/> + <signal name="activate" handler="cc_wwan_pin_apply_clicked_cb" swapped="yes"/> + </object> + </child> + </object> + </property> + </object> + </child> + </object> + </property> + + </object> + </child> + </object> + </child> + </object> + </child> <!-- ./internal-child --> + + <child type="action"> + <object class="GtkButton" id="cancel_button"> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Cancel</property> + <signal name="clicked" handler="gtk_widget_hide" swapped="yes"/> + </object> + </child> + <action-widgets> + <action-widget response="cancel">cancel_button</action-widget> + </action-widgets> + + </template> +</interface> diff --git a/panels/wwan/gnome-wwan-panel.desktop.in.in b/panels/wwan/gnome-wwan-panel.desktop.in.in new file mode 100644 index 0000000..dfe9409 --- /dev/null +++ b/panels/wwan/gnome-wwan-panel.desktop.in.in @@ -0,0 +1,15 @@ +[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=org.gnome.Settings-mobile-network-symbolic +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; +# 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/icons/meson.build b/panels/wwan/icons/meson.build new file mode 100644 index 0000000..88d015a --- /dev/null +++ b/panels/wwan/icons/meson.build @@ -0,0 +1,4 @@ +install_data( + 'scalable/org.gnome.Settings-mobile-network-symbolic.svg', + install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps') +) diff --git a/panels/wwan/icons/scalable/org.gnome.Settings-mobile-network-symbolic.svg b/panels/wwan/icons/scalable/org.gnome.Settings-mobile-network-symbolic.svg new file mode 100644 index 0000000..fe949f0 --- /dev/null +++ b/panels/wwan/icons/scalable/org.gnome.Settings-mobile-network-symbolic.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <path d="m 14 1 c -0.554688 0 -1 0.445312 -1 1 v 12 c 0 0.554688 0.445312 1 1 1 h 1 c 0.554688 0 1 -0.445312 1 -1 v -12 c 0 -0.554688 -0.445312 -1 -1 -1 z m -4 3 c -0.554688 0 -1 0.445312 -1 1 v 9 c 0 0.554688 0.445312 1 1 1 h 1 c 0.554688 0 1 -0.445312 1 -1 v -9 c 0 -0.554688 -0.445312 -1 -1 -1 z m -4 3 c -0.554688 0 -1 0.445312 -1 1 v 6 c 0 0.554688 0.445312 1 1 1 h 1 c 0.554688 0 1 -0.445312 1 -1 v -6 c 0 -0.554688 -0.445312 -1 -1 -1 z m -4 3 c -0.554688 0 -1 0.445312 -1 1 v 3 c 0 0.554688 0.445312 1 1 1 h 1 c 0.554688 0 1 -0.445312 1 -1 v -3 c 0 -0.554688 -0.445312 -1 -1 -1 z m 0 0" fill="#2e3436"/> +</svg> diff --git a/panels/wwan/meson.build b/panels/wwan/meson.build new file mode 100644 index 0000000..9b73b5b --- /dev/null +++ b/panels/wwan/meson.build @@ -0,0 +1,61 @@ + +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( + 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 +) + +subdir('icons') diff --git a/panels/wwan/wwan.gresource.xml b/panels/wwan/wwan.gresource.xml new file mode 100644 index 0000000..f128a16 --- /dev/null +++ b/panels/wwan/wwan.gresource.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/wwan"> + <file preprocess="xml-stripblanks">cc-wwan-panel.ui</file> + <file preprocess="xml-stripblanks">cc-wwan-device-page.ui</file> + <file preprocess="xml-stripblanks">cc-wwan-mode-dialog.ui</file> + <file preprocess="xml-stripblanks">cc-wwan-network-dialog.ui</file> + <file preprocess="xml-stripblanks">cc-wwan-details-dialog.ui</file> + <file preprocess="xml-stripblanks">cc-wwan-sim-lock-dialog.ui</file> + <file preprocess="xml-stripblanks">cc-wwan-apn-dialog.ui</file> + </gresource> +</gresources> |