diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4631a15 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/gnome-control-center-40.0.tar.xz diff --git a/.gnome-control-center.metadata b/.gnome-control-center.metadata new file mode 100644 index 0000000..e413afe --- /dev/null +++ b/.gnome-control-center.metadata @@ -0,0 +1 @@ +06a456d779b174a29ea65801133d2b85fcc02732 SOURCES/gnome-control-center-40.0.tar.xz diff --git a/SOURCES/distro-logo.patch b/SOURCES/distro-logo.patch new file mode 100644 index 0000000..d59e43a --- /dev/null +++ b/SOURCES/distro-logo.patch @@ -0,0 +1,195 @@ +From 4369e31ec541172e1c0d7c64645c7990e413bbca Mon Sep 17 00:00:00 2001 +From: Michael Catanzaro +Date: Tue, 9 Mar 2021 14:51:54 -0600 +Subject: [PATCH 1/3] info-overview: add build option to control distributor + logo + +Currently, we display a 256x256 version of the OS icon from +/etc/os-release. This is too big for my taste, and it's also not +sufficient for distros that want to display a logo that is not an icon. +For instance, because we no longer display the operating system name +immediately beneath the logo, it may be desirable to use a logo variant +that includes text. This patch adds a meson build option that +distributions can use to override the logo. + +Because the logo might include text, distributions may want to vary the +logo used in dark mode. A subsequent commit will add a second option for +this. +--- + meson.build | 6 ++++++ + meson_options.txt | 1 + + panels/info-overview/cc-info-overview-panel.c | 4 ++++ + 3 files changed, 11 insertions(+) + +diff --git a/meson.build b/meson.build +index e8333c0da..1661caa4b 100644 +--- a/meson.build ++++ b/meson.build +@@ -50,6 +50,12 @@ foreach define: set_defines + config_h.set_quoted(define[0], define[1]) + endforeach + ++distributor_logo = get_option('distributor_logo') ++if (distributor_logo != '') ++ config_h.set_quoted('DISTRIBUTOR_LOGO', distributor_logo, ++ description: 'Define to absolute path of distributor logo') ++endif ++ + # meson does not support octal values, so it must be handled as a + # string. See: https://github.com/mesonbuild/meson/issues/2047 + config_h.set('USER_DIR_MODE', '0700', +diff --git a/meson_options.txt b/meson_options.txt +index 1b7b54810..93e551373 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -8,3 +8,4 @@ option('tracing', type: 'boolean', value: false, description: 'add extra debuggi + option('wayland', type: 'boolean', value: true, description: 'build with Wayland support') + option('profile', type: 'combo', choices: ['default','development'], value: 'default') + option('malcontent', type: 'boolean', value: false, description: 'build with malcontent support') ++option('distributor_logo', type: 'string', description: 'absolute path to distributor logo for the About panel') +diff --git a/panels/info-overview/cc-info-overview-panel.c b/panels/info-overview/cc-info-overview-panel.c +index bd0e07762..95a5904df 100644 +--- a/panels/info-overview/cc-info-overview-panel.c ++++ b/panels/info-overview/cc-info-overview-panel.c +@@ -869,6 +869,9 @@ cc_info_panel_row_activated_cb (CcInfoOverviewPanel *self, + static void + setup_os_logo (CcInfoOverviewPanel *panel) + { ++#ifdef DISTRIBUTOR_LOGO ++ gtk_image_set_from_file (panel->os_logo, DISTRIBUTOR_LOGO); ++#else + g_autofree char *logo_name = g_get_os_info ("LOGO"); + if (logo_name != NULL) + { +@@ -879,6 +882,7 @@ setup_os_logo (CcInfoOverviewPanel *panel) + { + gtk_image_set_from_resource (panel->os_logo, "/org/gnome/control-center/info-overview/GnomeLogoVerticalMedium.svg"); + } ++#endif + } + + static void +-- +GitLab + + +From f08669767ca87ff99fc08e1a7334c8f2e7f18f0b Mon Sep 17 00:00:00 2001 +From: Michael Catanzaro +Date: Tue, 9 Mar 2021 16:02:46 -0600 +Subject: [PATCH 2/3] info-overview: add build option to specify a dark mode + logo variant + +Let's allow distributions to specify a different logo to use when using +a dark GTK theme. This is best-effort only since it relies on the +convention that dark themes must end with "dark" and therefore will fail +for a theme named "midnight" or anything that doesn't match convention. +--- + meson.build | 5 ++++ + meson_options.txt | 1 + + panels/info-overview/cc-info-overview-panel.c | 27 +++++++++++++++++++ + 3 files changed, 33 insertions(+) + +diff --git a/meson.build b/meson.build +index 1661caa4b..124171626 100644 +--- a/meson.build ++++ b/meson.build +@@ -54,6 +54,11 @@ distributor_logo = get_option('distributor_logo') + if (distributor_logo != '') + config_h.set_quoted('DISTRIBUTOR_LOGO', distributor_logo, + description: 'Define to absolute path of distributor logo') ++ dark_mode_distributor_logo = get_option('dark_mode_distributor_logo') ++ if (dark_mode_distributor_logo != '') ++ config_h.set_quoted('DARK_MODE_DISTRIBUTOR_LOGO', dark_mode_distributor_logo, ++ description: 'Define to absolute path of distributor logo for use in dark mode') ++ endif + endif + + # meson does not support octal values, so it must be handled as a +diff --git a/meson_options.txt b/meson_options.txt +index 93e551373..5305c8606 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -9,3 +9,4 @@ option('wayland', type: 'boolean', value: true, description: 'build with Wayland + option('profile', type: 'combo', choices: ['default','development'], value: 'default') + option('malcontent', type: 'boolean', value: false, description: 'build with malcontent support') + option('distributor_logo', type: 'string', description: 'absolute path to distributor logo for the About panel') ++option('dark_mode_distributor_logo', type: 'string', description: 'absolute path to distributor logo dark mode variant') +diff --git a/panels/info-overview/cc-info-overview-panel.c b/panels/info-overview/cc-info-overview-panel.c +index 95a5904df..cb20e16b1 100644 +--- a/panels/info-overview/cc-info-overview-panel.c ++++ b/panels/info-overview/cc-info-overview-panel.c +@@ -866,10 +866,37 @@ cc_info_panel_row_activated_cb (CcInfoOverviewPanel *self, + open_software_update (self); + } + ++#ifdef DARK_MODE_DISTRIBUTOR_LOGO ++static gboolean ++is_dark_mode (CcInfoOverviewPanel *panel) ++{ ++ GdkScreen *screen; ++ GtkSettings *settings; ++ g_autofree char *theme_name = NULL; ++ ++ theme_name = g_strdup (g_getenv ("GTK_THEME")); ++ if (theme_name != NULL) ++ return g_str_has_suffix (theme_name, "dark"); ++ ++ screen = gtk_widget_get_screen (GTK_WIDGET (panel)); ++ settings = gtk_settings_get_for_screen (screen); ++ ++ g_object_get (settings, "gtk-theme-name", &theme_name, NULL); ++ return theme_name != NULL && g_str_has_suffix (theme_name, "dark"); ++} ++#endif ++ + static void + setup_os_logo (CcInfoOverviewPanel *panel) + { + #ifdef DISTRIBUTOR_LOGO ++#ifdef DARK_MODE_DISTRIBUTOR_LOGO ++ if (is_dark_mode (panel)) ++ { ++ gtk_image_set_from_file (panel->os_logo, DARK_MODE_DISTRIBUTOR_LOGO); ++ return; ++ } ++#endif + gtk_image_set_from_file (panel->os_logo, DISTRIBUTOR_LOGO); + #else + g_autofree char *logo_name = g_get_os_info ("LOGO"); +-- +GitLab + + +From c379ccc4e8f0bcdee78361f134ba29d3a25f7528 Mon Sep 17 00:00:00 2001 +From: Michael Catanzaro +Date: Wed, 10 Mar 2021 11:30:57 -0600 +Subject: [PATCH 3/3] info-overview: reduce size of logo when using icon from + /etc/os-release + +When using the icon from /etc/os-release, display it as 128x128 rather +than 256x256. In distributions that don't ship a 128x128, such as +Fedora, this results in the 256x256 icon being scaled down to a +reasonable size. 256x256 is so large here as to be clearly undesirable. +128x128 is also the size that Ubuntu uses in its downstream patch. Might +as well reduce the need for patching as far as possible, even though +Fedora doesn't plan to use this codepath. +--- + panels/info-overview/cc-info-overview-panel.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/panels/info-overview/cc-info-overview-panel.c b/panels/info-overview/cc-info-overview-panel.c +index cb20e16b1..3575b90c5 100644 +--- a/panels/info-overview/cc-info-overview-panel.c ++++ b/panels/info-overview/cc-info-overview-panel.c +@@ -903,7 +903,7 @@ setup_os_logo (CcInfoOverviewPanel *panel) + if (logo_name != NULL) + { + gtk_image_set_from_icon_name (panel->os_logo, logo_name, GTK_ICON_SIZE_INVALID); +- gtk_image_set_pixel_size (panel->os_logo, 256); ++ gtk_image_set_pixel_size (panel->os_logo, 128); + } + else + { +-- +GitLab + diff --git a/SOURCES/gnome-control-center-Drop-the-unused-build-dependency-on-Grilo.patch b/SOURCES/gnome-control-center-Drop-the-unused-build-dependency-on-Grilo.patch new file mode 100644 index 0000000..2287b1c --- /dev/null +++ b/SOURCES/gnome-control-center-Drop-the-unused-build-dependency-on-Grilo.patch @@ -0,0 +1,1385 @@ +From e2d3f71a37a7e587b0af2a9818377ec1ac098e79 Mon Sep 17 00:00:00 2001 +From: Debarshi Ray +Date: Tue, 20 Apr 2021 23:00:28 +0200 +Subject: [PATCH] background: Drop the unused build dependency on Grilo + +The code that uses Grilo to fetch the user's Flickr photos isn't +actually used by the Background panel, to the extent that the final +binary doesn't even link against Grilo. Getting rid of this unused code +will ensure that distributors aren't needlessly pulling in this +dependency. + +This patch has been customized for RHEL 9 to skip the .gitlab-ci.yml +file, which isn't part of the 'ninja dist' generated tarball. + +https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/1345 +--- + build-aux/flatpak/org.gnome.Settings.json | 17 - + panels/background/bg-pictures-source.c | 855 ------------------ + panels/background/bg-pictures-source.h | 46 - + panels/background/cc-background-chooser.c | 1 - + panels/background/cc-background-grilo-miner.c | 315 ------- + panels/background/cc-background-grilo-miner.h | 31 - + panels/background/cc-background-panel.c | 2 - + panels/background/meson.build | 4 - + 8 files changed, 1271 deletions(-) + delete mode 100644 panels/background/bg-pictures-source.c + delete mode 100644 panels/background/bg-pictures-source.h + delete mode 100644 panels/background/cc-background-grilo-miner.c + delete mode 100644 panels/background/cc-background-grilo-miner.h + +diff --git a/build-aux/flatpak/org.gnome.Settings.json b/build-aux/flatpak/org.gnome.Settings.json +index 3e19894b0484..b64b4c2fdbc5 100644 +--- a/build-aux/flatpak/org.gnome.Settings.json ++++ b/build-aux/flatpak/org.gnome.Settings.json +@@ -485,23 +485,6 @@ + } + ] + }, +- { +- "name" : "grilo", +- "buildsystem" : "meson", +- "config-opts" : [ +- "-Denable-grl-pls=false", +- "-Denable-gtk-doc=false", +- "-Denable-introspection=false", +- "-Denable-test-ui=false", +- "-Denable-vala=false" +- ], +- "sources" : [ +- { +- "type" : "git", +- "url" : "https://gitlab.gnome.org/GNOME/grilo.git" +- } +- ] +- }, + { + "name" : "openldap", + "buildsystem" : "autotools", +diff --git a/panels/background/bg-pictures-source.c b/panels/background/bg-pictures-source.c +deleted file mode 100644 +index 3a3027b1391e..000000000000 +--- a/panels/background/bg-pictures-source.c ++++ /dev/null +@@ -1,855 +0,0 @@ +-/* bg-pictures-source.c */ +-/* +- * Copyright (C) 2010 Intel, Inc +- * +- * 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 2 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * You should have received a copy of the GNU General Public License +- * along with this program; if not, see . +- * +- * Author: Thomas Wood +- * +- */ +- +-#include +- +-#include "bg-pictures-source.h" +- +-#include "cc-background-grilo-miner.h" +-#include "cc-background-item.h" +- +-#include +-#include +-#include +-#include +-#include +-#include +- +-#define ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_NAME "," \ +- G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \ +- G_FILE_ATTRIBUTE_TIME_MODIFIED +- +-struct _BgPicturesSource +-{ +- BgSource parent_instance; +- +- GCancellable *cancellable; +- +- CcBackgroundGriloMiner *grl_miner; +- +- GFileMonitor *picture_dir_monitor; +- GFileMonitor *cache_dir_monitor; +- +- GHashTable *known_items; +-}; +- +-G_DEFINE_TYPE (BgPicturesSource, bg_pictures_source, BG_TYPE_SOURCE) +- +-const char * const content_types[] = { +- "image/png", +- "image/jp2", +- "image/jpeg", +- "image/bmp", +- "image/svg+xml", +- "image/x-portable-anymap", +- NULL +-}; +- +-const char * const screenshot_types[] = { +- "image/png", +- NULL +-}; +- +-static char *bg_pictures_source_get_unique_filename (const char *uri); +- +-static void picture_opened_for_read (GObject *source_object, GAsyncResult *res, gpointer user_data); +- +-static void +-bg_pictures_source_dispose (GObject *object) +-{ +- BgPicturesSource *source = BG_PICTURES_SOURCE (object); +- +- if (source->cancellable) +- { +- g_cancellable_cancel (source->cancellable); +- g_clear_object (&source->cancellable); +- } +- +- g_clear_object (&source->grl_miner); +- +- G_OBJECT_CLASS (bg_pictures_source_parent_class)->dispose (object); +-} +- +-static void +-bg_pictures_source_finalize (GObject *object) +-{ +- BgPicturesSource *bg_source = BG_PICTURES_SOURCE (object); +- +- g_clear_pointer (&bg_source->known_items, g_hash_table_destroy); +- +- g_clear_object (&bg_source->picture_dir_monitor); +- g_clear_object (&bg_source->cache_dir_monitor); +- +- G_OBJECT_CLASS (bg_pictures_source_parent_class)->finalize (object); +-} +- +-static void +-bg_pictures_source_class_init (BgPicturesSourceClass *klass) +-{ +- GObjectClass *object_class = G_OBJECT_CLASS (klass); +- +- object_class->dispose = bg_pictures_source_dispose; +- object_class->finalize = bg_pictures_source_finalize; +-} +- +-static void +-remove_placeholder (BgPicturesSource *bg_source, +- CcBackgroundItem *item) +-{ +- GListStore *store; +- guint i; +- +- store = bg_source_get_liststore (BG_SOURCE (bg_source)); +- +- for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++) +- { +- g_autoptr(CcBackgroundItem) item_n = NULL; +- +- item_n = g_list_model_get_item (G_LIST_MODEL (store), i); +- +- if (item_n == item) +- { +- g_list_store_remove (store, i); +- break; +- } +- } +-} +- +-static gboolean +-picture_needs_rotation (GdkPixbuf *pixbuf) +-{ +- const gchar *str; +- +- str = gdk_pixbuf_get_option (pixbuf, "orientation"); +- if (str == NULL) +- return FALSE; +- +- if (*str == '5' || *str == '6' || *str == '7' || *str == '8') +- return TRUE; +- +- return FALSE; +-} +- +-static GdkPixbuf * +-swap_rotated_pixbuf (GdkPixbuf *pixbuf) +-{ +- GdkPixbuf *tmp_pixbuf; +- +- tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf); +- if (tmp_pixbuf == NULL) +- return pixbuf; +- +- g_object_unref (pixbuf); +- return tmp_pixbuf; +-} +- +-static int +-sort_func (gconstpointer a, +- gconstpointer b, +- gpointer user_data) +-{ +- CcBackgroundItem *item_a; +- CcBackgroundItem *item_b; +- guint64 modified_a; +- guint64 modified_b; +- int retval; +- +- item_a = (CcBackgroundItem *) a; +- item_b = (CcBackgroundItem *) b; +- modified_a = cc_background_item_get_modified (item_a); +- modified_b = cc_background_item_get_modified (item_b); +- +- retval = modified_b - modified_a; +- +- return retval; +-} +- +-static void +-picture_scaled (GObject *source_object, +- GAsyncResult *res, +- gpointer user_data) +-{ +- BgPicturesSource *bg_source; +- CcBackgroundItem *item; +- g_autoptr(GError) error = NULL; +- g_autoptr(GdkPixbuf) pixbuf = NULL; +- const char *software; +- const char *uri; +- GListStore *store; +- cairo_surface_t *surface = NULL; +- int scale_factor; +- gboolean rotation_applied; +- +- item = g_object_get_data (source_object, "item"); +- pixbuf = gdk_pixbuf_new_from_stream_finish (res, &error); +- if (pixbuf == NULL) +- { +- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) +- { +- g_warning ("Failed to load image: %s", error->message); +- remove_placeholder (BG_PICTURES_SOURCE (user_data), item); +- } +- +- return; +- } +- +- /* since we were not cancelled, we can now cast user_data +- * back to BgPicturesSource. +- */ +- bg_source = BG_PICTURES_SOURCE (user_data); +- store = bg_source_get_liststore (BG_SOURCE (bg_source)); +- uri = cc_background_item_get_uri (item); +- if (uri == NULL) +- uri = cc_background_item_get_source_url (item); +- +- /* Ignore screenshots */ +- software = gdk_pixbuf_get_option (pixbuf, "tEXt::Software"); +- if (software != NULL && +- g_str_equal (software, "gnome-screenshot")) +- { +- g_debug ("Ignored URL '%s' as it's a screenshot from gnome-screenshot", uri); +- remove_placeholder (BG_PICTURES_SOURCE (user_data), item); +- return; +- } +- +- /* Process embedded orientation */ +- rotation_applied = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "rotation-applied")); +- +- if (!rotation_applied && picture_needs_rotation (pixbuf)) +- { +- /* the width and height of pixbuf we requested are wrong for EXIF +- * orientations 5, 6, 7 and 8. the file has to be reloaded. */ +- g_autoptr(GFile) file = NULL; +- +- file = g_file_new_for_uri (uri); +- g_object_set_data (G_OBJECT (item), "needs-rotation", GINT_TO_POINTER (TRUE)); +- g_object_set_data_full (G_OBJECT (file), "item", g_object_ref (item), g_object_unref); +- g_file_read_async (G_FILE (file), G_PRIORITY_DEFAULT, +- bg_source->cancellable, +- picture_opened_for_read, bg_source); +- return; +- } +- +- pixbuf = swap_rotated_pixbuf (pixbuf); +- +- scale_factor = bg_source_get_scale_factor (BG_SOURCE (bg_source)); +- surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale_factor, NULL); +- cc_background_item_load (item, NULL); +- +- /* insert the item into the liststore */ +- g_list_store_insert_sorted (store, item, sort_func, bg_source); +- +- g_hash_table_insert (bg_source->known_items, +- bg_pictures_source_get_unique_filename (uri), +- GINT_TO_POINTER (TRUE)); +- +- g_clear_pointer (&surface, cairo_surface_destroy); +-} +- +-static void +-picture_opened_for_read (GObject *source_object, +- GAsyncResult *res, +- gpointer user_data) +-{ +- BgPicturesSource *bg_source; +- CcBackgroundItem *item; +- g_autoptr(GFileInputStream) stream = NULL; +- g_autoptr(GError) error = NULL; +- gint thumbnail_height; +- gint thumbnail_width; +- gboolean needs_rotation; +- +- item = g_object_get_data (source_object, "item"); +- stream = g_file_read_finish (G_FILE (source_object), res, &error); +- if (stream == NULL) +- { +- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) +- { +- g_autofree gchar *filename = g_file_get_path (G_FILE (source_object)); +- g_warning ("Failed to load picture '%s': %s", filename, error->message); +- remove_placeholder (BG_PICTURES_SOURCE (user_data), item); +- } +- +- return; +- } +- +- /* since we were not cancelled, we can now cast user_data +- * back to BgPicturesSource. +- */ +- bg_source = BG_PICTURES_SOURCE (user_data); +- +- needs_rotation = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "needs-rotation")); +- if (needs_rotation) +- { +- /* swap width and height for EXIF orientations that need it */ +- thumbnail_width = bg_source_get_thumbnail_height (BG_SOURCE (bg_source)); +- thumbnail_height = bg_source_get_thumbnail_width (BG_SOURCE (bg_source)); +- g_object_set_data (G_OBJECT (item), "rotation-applied", GINT_TO_POINTER (TRUE)); +- } +- else +- { +- thumbnail_width = bg_source_get_thumbnail_width (BG_SOURCE (bg_source)); +- thumbnail_height = bg_source_get_thumbnail_height (BG_SOURCE (bg_source)); +- } +- +- g_object_set_data_full (G_OBJECT (stream), "item", g_object_ref (item), g_object_unref); +- gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (stream), +- thumbnail_width, thumbnail_height, +- TRUE, +- bg_source->cancellable, +- picture_scaled, bg_source); +-} +- +-static void +-picture_copied_for_read (GObject *source_object, +- GAsyncResult *res, +- gpointer user_data) +-{ +- BgPicturesSource *bg_source; +- CcBackgroundItem *item; +- g_autoptr(GError) error = NULL; +- GFile *thumbnail_file = G_FILE (source_object); +- GFile *native_file; +- +- if (!g_file_copy_finish (thumbnail_file, res, &error)) +- { +- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) +- return; +- else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) +- { +- g_autofree gchar *uri = NULL; +- +- uri = g_file_get_uri (thumbnail_file); +- g_warning ("Failed to download '%s': %s", uri, error->message); +- return; +- } +- } +- +- bg_source = BG_PICTURES_SOURCE (user_data); +- +- native_file = g_object_get_data (G_OBJECT (thumbnail_file), "native-file"); +- item = g_object_get_data (G_OBJECT (thumbnail_file), "item"); +- g_object_set_data_full (G_OBJECT (native_file), "item", g_object_ref (item), g_object_unref); +- g_file_read_async (native_file, +- G_PRIORITY_DEFAULT, +- bg_source->cancellable, +- picture_opened_for_read, +- bg_source); +-} +- +-static gboolean +-in_content_types (const char *content_type) +-{ +- guint i; +- for (i = 0; content_types[i]; i++) +- if (g_str_equal (content_types[i], content_type)) +- return TRUE; +- return FALSE; +-} +- +-static GFile * +-bg_pictures_source_get_cache_file (void) +-{ +- g_autofree gchar *path = NULL; +- GFile *file; +- +- path = bg_pictures_source_get_cache_path (); +- file = g_file_new_for_path (path); +- +- return file; +-} +- +-static gboolean +-add_single_file (BgPicturesSource *bg_source, +- GFile *file, +- const gchar *content_type, +- guint64 mtime) +-{ +- g_autoptr(CcBackgroundItem) item = NULL; +- CcBackgroundItemFlags flags = 0; +- g_autofree gchar *source_uri = NULL; +- g_autofree gchar *uri = NULL; +- gboolean needs_download; +- gboolean retval = FALSE; +- const gchar *pictures_path; +- g_autoptr(GFile) pictures_dir = NULL; +- g_autoptr(GFile) cache_dir = NULL; +- GrlMedia *media; +- +- /* find png and jpeg files */ +- if (!content_type) +- goto out; +- if (!in_content_types (content_type)) +- goto out; +- +- /* create a new CcBackgroundItem */ +- uri = g_file_get_uri (file); +- +- pictures_path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES); +- if (pictures_path == NULL) +- pictures_path = g_get_home_dir (); +- pictures_dir = g_file_new_for_path (pictures_path); +- cache_dir = bg_pictures_source_get_cache_file (); +- needs_download = !g_file_has_parent (file, pictures_dir) && +- !g_file_has_parent (file, cache_dir); +- +- if (!needs_download) +- { +- source_uri = g_strdup (uri); +- flags |= CC_BACKGROUND_ITEM_HAS_URI; +- } +- else +- { +- source_uri = g_steal_pointer (&uri); +- } +- +- item = cc_background_item_new (uri); +- flags |= CC_BACKGROUND_ITEM_HAS_SHADING | CC_BACKGROUND_ITEM_HAS_PLACEMENT; +- g_object_set (G_OBJECT (item), +- "flags", flags, +- "shading", G_DESKTOP_BACKGROUND_SHADING_SOLID, +- "placement", G_DESKTOP_BACKGROUND_STYLE_ZOOM, +- "modified", mtime, +- "needs-download", needs_download, +- "source-url", source_uri, +- NULL); +- +- media = g_object_get_data (G_OBJECT (file), "grl-media"); +- if (media == NULL) +- { +- g_object_set_data_full (G_OBJECT (file), "item", g_object_ref (item), g_object_unref); +- g_file_read_async (file, G_PRIORITY_DEFAULT, +- bg_source->cancellable, +- picture_opened_for_read, bg_source); +- } +- else +- { +- g_autoptr(GFile) native_file = NULL; +- g_autoptr(GFile) thumbnail_file = NULL; +- g_autofree gchar *native_dir = NULL; +- g_autofree gchar *native_path = NULL; +- const gchar *title; +- const gchar *thumbnail_uri; +- +- title = grl_media_get_title (media); +- g_object_set (G_OBJECT (item), "name", title, NULL); +- +- thumbnail_uri = grl_media_get_thumbnail (media); +- thumbnail_file = g_file_new_for_uri (thumbnail_uri); +- +- native_path = gnome_desktop_thumbnail_path_for_uri (source_uri, GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE); +- native_file = g_file_new_for_path (native_path); +- +- native_dir = g_path_get_dirname (native_path); +- g_mkdir_with_parents (native_dir, USER_DIR_MODE); +- +- g_object_set_data_full (G_OBJECT (thumbnail_file), "item", g_object_ref (item), g_object_unref); +- g_object_set_data_full (G_OBJECT (thumbnail_file), +- "native-file", +- g_object_ref (native_file), +- g_object_unref); +- g_file_copy_async (thumbnail_file, +- native_file, +- G_FILE_COPY_ALL_METADATA, +- G_PRIORITY_DEFAULT, +- bg_source->cancellable, +- NULL, +- NULL, +- picture_copied_for_read, +- bg_source); +- } +- +- retval = TRUE; +- +- out: +- return retval; +-} +- +-static gboolean +-add_single_file_from_info (BgPicturesSource *bg_source, +- GFile *file, +- GFileInfo *info) +-{ +- const gchar *content_type; +- guint64 mtime; +- +- content_type = g_file_info_get_content_type (info); +- mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); +- return add_single_file (bg_source, file, content_type, mtime); +-} +- +-static gboolean +-add_single_file_from_media (BgPicturesSource *bg_source, +- GFile *file, +- GrlMedia *media) +-{ +- GDateTime *mtime; +- const gchar *content_type; +- gint64 mtime_unix; +- +- content_type = grl_media_get_mime (media); +- +- /* only GRL_METADATA_KEY_CREATION_DATE is implemented in the Flickr +- * plugin, GRL_METADATA_KEY_MODIFICATION_DATE is not +- */ +- mtime = grl_media_get_creation_date (media); +- if (!mtime) +- mtime = grl_media_get_modification_date (media); +- if (mtime) +- mtime_unix = g_date_time_to_unix (mtime); +- else +- mtime_unix = g_get_real_time () / G_USEC_PER_SEC; +- +- return add_single_file (bg_source, file, content_type, (guint64) mtime_unix); +-} +- +-gboolean +-bg_pictures_source_add (BgPicturesSource *bg_source, +- const char *uri, +- GtkTreeRowReference **ret_row_ref) +-{ +- g_autoptr(GFile) file = NULL; +- GFileInfo *info; +- gboolean retval; +- +- file = g_file_new_for_uri (uri); +- info = g_file_query_info (file, ATTRIBUTES, G_FILE_QUERY_INFO_NONE, NULL, NULL); +- if (info == NULL) +- return FALSE; +- +- retval = add_single_file_from_info (bg_source, file, info); +- +- return retval; +-} +- +-gboolean +-bg_pictures_source_remove (BgPicturesSource *bg_source, +- const char *uri) +-{ +- GListStore *store; +- gboolean retval; +- guint i; +- +- retval = FALSE; +- store = bg_source_get_liststore (BG_SOURCE (bg_source)); +- +- for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++) +- { +- g_autoptr(CcBackgroundItem) tmp_item = NULL; +- const char *tmp_uri; +- +- tmp_item = g_list_model_get_item (G_LIST_MODEL (store), i); +- tmp_uri = cc_background_item_get_uri (tmp_item); +- if (g_str_equal (tmp_uri, uri)) +- { +- char *uuid; +- uuid = bg_pictures_source_get_unique_filename (uri); +- g_hash_table_insert (bg_source->known_items, +- uuid, NULL); +- +- g_list_store_remove (store, i); +- retval = TRUE; +- break; +- } +- } +- return retval; +-} +- +-static int +-file_sort_func (gconstpointer a, +- gconstpointer b) +-{ +- GFileInfo *file_a = G_FILE_INFO (a); +- GFileInfo *file_b = G_FILE_INFO (b); +- guint64 modified_a, modified_b; +- +- modified_a = g_file_info_get_attribute_uint64 (file_a, G_FILE_ATTRIBUTE_TIME_MODIFIED); +- +- modified_b = g_file_info_get_attribute_uint64 (file_b, G_FILE_ATTRIBUTE_TIME_MODIFIED); +- +- return modified_b - modified_a; +-} +- +-static void +-file_info_async_ready (GObject *source, +- GAsyncResult *res, +- gpointer user_data) +-{ +- BgPicturesSource *bg_source; +- GList *files, *l; +- g_autoptr(GError) err = NULL; +- GFile *parent; +- +- files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source), +- res, +- &err); +- if (err) +- { +- if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) +- g_warning ("Could not get pictures file information: %s", err->message); +- +- g_list_foreach (files, (GFunc) g_object_unref, NULL); +- g_list_free (files); +- return; +- } +- +- bg_source = BG_PICTURES_SOURCE (user_data); +- +- parent = g_file_enumerator_get_container (G_FILE_ENUMERATOR (source)); +- +- files = g_list_sort (files, file_sort_func); +- +- /* iterate over the available files */ +- for (l = files; l; l = g_list_next (l)) +- { +- GFileInfo *info = l->data; +- g_autoptr(GFile) file = NULL; +- +- file = g_file_get_child (parent, g_file_info_get_name (info)); +- +- add_single_file_from_info (bg_source, file, info); +- } +- +- g_list_foreach (files, (GFunc) g_object_unref, NULL); +- g_list_free (files); +-} +- +-static void +-dir_enum_async_ready (GObject *s, +- GAsyncResult *res, +- gpointer user_data) +-{ +- BgPicturesSource *source = (BgPicturesSource *) user_data; +- g_autoptr(GFileEnumerator) enumerator = NULL; +- g_autoptr(GError) err = NULL; +- +- enumerator = g_file_enumerate_children_finish (G_FILE (s), res, &err); +- +- if (err) +- { +- if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) +- g_warning ("Could not fill pictures source: %s", err->message); +- return; +- } +- +- /* get the files */ +- g_file_enumerator_next_files_async (enumerator, +- G_MAXINT, +- G_PRIORITY_LOW, +- source->cancellable, +- file_info_async_ready, +- user_data); +-} +- +-char * +-bg_pictures_source_get_cache_path (void) +-{ +- return g_build_filename (g_get_user_cache_dir (), +- "gnome-control-center", +- "backgrounds", +- NULL); +-} +- +-static char * +-bg_pictures_source_get_unique_filename (const char *uri) +-{ +- g_autoptr(GChecksum) csum = NULL; +- char *ret; +- +- csum = g_checksum_new (G_CHECKSUM_SHA256); +- g_checksum_update (csum, (guchar *) uri, -1); +- ret = g_strdup (g_checksum_get_string (csum)); +- +- return ret; +-} +- +-char * +-bg_pictures_source_get_unique_path (const char *uri) +-{ +- g_autoptr(GFile) parent = NULL; +- g_autoptr(GFile) file = NULL; +- g_autofree gchar *cache_path = NULL; +- g_autofree gchar *filename = NULL; +- +- cache_path = bg_pictures_source_get_cache_path (); +- parent = g_file_new_for_path (cache_path); +- +- filename = bg_pictures_source_get_unique_filename (uri); +- file = g_file_get_child (parent, filename); +- +- return g_file_get_path (file); +-} +- +-gboolean +-bg_pictures_source_is_known (BgPicturesSource *bg_source, +- const char *uri) +-{ +- g_autofree gchar *uuid = NULL; +- +- uuid = bg_pictures_source_get_unique_filename (uri); +- +- return GPOINTER_TO_INT (g_hash_table_lookup (bg_source->known_items, uuid)); +-} +- +-static void +-file_info_ready (GObject *object, +- GAsyncResult *res, +- gpointer user_data) +-{ +- GFileInfo *info; +- GError *error = NULL; +- GFile *file = G_FILE (object); +- +- info = g_file_query_info_finish (file, res, &error); +- +- if (!info) +- { +- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) +- g_warning ("Problem looking up file info: %s", error->message); +- g_clear_error (&error); +- return; +- } +- +- add_single_file_from_info (BG_PICTURES_SOURCE (user_data), file, info); +-} +- +-static void +-file_added (GFile *file, +- BgPicturesSource *self) +-{ +- g_autofree gchar *uri = NULL; +- uri = g_file_get_uri (file); +- +- if (!bg_pictures_source_is_known (self, uri)) +- { +- g_file_query_info_async (file, +- ATTRIBUTES, +- G_FILE_QUERY_INFO_NONE, +- G_PRIORITY_LOW, +- NULL, +- file_info_ready, +- self); +- } +-} +- +-static void +-files_changed_cb (BgPicturesSource *self, +- GFile *file, +- GFile *other_file, +- GFileMonitorEvent event_type) +-{ +- g_autofree gchar *uri = NULL; +- +- switch (event_type) +- { +- case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: +- file_added (file, self); +- break; +- +- case G_FILE_MONITOR_EVENT_DELETED: +- uri = g_file_get_uri (file); +- bg_pictures_source_remove (self, uri); +- break; +- +- default: +- return; +- } +-} +- +-static GFileMonitor * +-monitor_path (BgPicturesSource *self, +- const char *path) +-{ +- GFileMonitor *monitor; +- g_autoptr(GFile) dir = NULL; +- +- g_mkdir_with_parents (path, USER_DIR_MODE); +- +- dir = g_file_new_for_path (path); +- g_file_enumerate_children_async (dir, +- ATTRIBUTES, +- G_FILE_QUERY_INFO_NONE, +- G_PRIORITY_LOW, self->cancellable, +- dir_enum_async_ready, self); +- +- monitor = g_file_monitor_directory (dir, +- G_FILE_MONITOR_NONE, +- self->cancellable, +- NULL); +- +- if (monitor) +- g_signal_connect_object (monitor, +- "changed", +- G_CALLBACK (files_changed_cb), +- self, G_CONNECT_SWAPPED); +- +- return monitor; +-} +- +-static void +-media_found_cb (BgPicturesSource *self, GrlMedia *media) +-{ +- g_autoptr(GFile) file = NULL; +- const gchar *uri; +- +- uri = grl_media_get_url (media); +- file = g_file_new_for_uri (uri); +- g_object_set_data_full (G_OBJECT (file), "grl-media", g_object_ref (media), g_object_unref); +- add_single_file_from_media (self, file, media); +-} +- +-static void +-bg_pictures_source_init (BgPicturesSource *self) +-{ +- const gchar *pictures_path; +- g_autofree gchar *cache_path = NULL; +- +- self->cancellable = g_cancellable_new (); +- self->known_items = g_hash_table_new_full (g_str_hash, +- g_str_equal, +- (GDestroyNotify) g_free, +- NULL); +- +- pictures_path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES); +- if (pictures_path == NULL) +- pictures_path = g_get_home_dir (); +- +- self->picture_dir_monitor = monitor_path (self, pictures_path); +- +- cache_path = bg_pictures_source_get_cache_path (); +- self->cache_dir_monitor = monitor_path (self, cache_path); +- +- self->grl_miner = cc_background_grilo_miner_new (); +- g_signal_connect_object (self->grl_miner, "media-found", G_CALLBACK (media_found_cb), self, G_CONNECT_SWAPPED); +- cc_background_grilo_miner_start (self->grl_miner); +-} +- +-BgPicturesSource * +-bg_pictures_source_new (GtkWidget *widget) +-{ +- return g_object_new (BG_TYPE_PICTURES_SOURCE, "widget", widget, NULL); +-} +- +-const char * const * +-bg_pictures_get_support_content_types (void) +-{ +- return content_types; +-} +diff --git a/panels/background/bg-pictures-source.h b/panels/background/bg-pictures-source.h +deleted file mode 100644 +index f62cbe532e26..000000000000 +--- a/panels/background/bg-pictures-source.h ++++ /dev/null +@@ -1,46 +0,0 @@ +-/* bg-pictures-source.h */ +-/* +- * Copyright (C) 2010 Intel, Inc +- * +- * 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 2 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * You should have received a copy of the GNU General Public License +- * along with this program; if not, see . +- * +- * Author: Thomas Wood +- * +- */ +- +-#pragma once +- +-#include +-#include "bg-source.h" +-#include "cc-background-item.h" +- +-G_BEGIN_DECLS +- +-#define BG_TYPE_PICTURES_SOURCE (bg_pictures_source_get_type ()) +-G_DECLARE_FINAL_TYPE (BgPicturesSource, bg_pictures_source, BG, PICTURES_SOURCE, BgSource) +- +-BgPicturesSource *bg_pictures_source_new (GtkWidget *widget); +-char *bg_pictures_source_get_cache_path (void); +-char *bg_pictures_source_get_unique_path(const char *uri); +-gboolean bg_pictures_source_add (BgPicturesSource *bg_source, +- const char *uri, +- GtkTreeRowReference **ret_row_ref); +-gboolean bg_pictures_source_remove (BgPicturesSource *bg_source, +- const char *uri); +-gboolean bg_pictures_source_is_known (BgPicturesSource *bg_source, +- const char *uri); +- +-const char * const * bg_pictures_get_support_content_types (void); +- +-G_END_DECLS +diff --git a/panels/background/cc-background-chooser.c b/panels/background/cc-background-chooser.c +index 6c8f56136271..04fd85c477aa 100644 +--- a/panels/background/cc-background-chooser.c ++++ b/panels/background/cc-background-chooser.c +@@ -25,7 +25,6 @@ + #include + + #include "bg-colors-source.h" +-#include "bg-pictures-source.h" + #include "bg-recent-source.h" + #include "bg-wallpapers-source.h" + #include "cc-background-chooser.h" +diff --git a/panels/background/cc-background-grilo-miner.c b/panels/background/cc-background-grilo-miner.c +deleted file mode 100644 +index 85c09daf6928..000000000000 +--- a/panels/background/cc-background-grilo-miner.c ++++ /dev/null +@@ -1,315 +0,0 @@ +-/* +- * Copyright (C) 2014 Red Hat, Inc. +- * +- * 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 2 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 . +- */ +- +-#include +- +-#include +-#include +- +-#define GOA_API_IS_SUBJECT_TO_CHANGE +-#include +- +-#include "bg-pictures-source.h" +-#include "cc-background-grilo-miner.h" +- +-struct _CcBackgroundGriloMiner +-{ +- GObject parent_instance; +- +- GCancellable *cancellable; +- GList *accounts; +-}; +- +-G_DEFINE_TYPE (CcBackgroundGriloMiner, cc_background_grilo_miner, G_TYPE_OBJECT) +- +-enum +-{ +- MEDIA_FOUND, +- LAST_SIGNAL +-}; +- +-static guint signals[LAST_SIGNAL] = { 0 }; +- +-#define REMOTE_ITEM_COUNT 50 +- +-static gchar * +-get_grilo_id (GoaObject *goa_object) +-{ +- GoaAccount *account; +- +- account = goa_object_peek_account (goa_object); +- return g_strdup_printf ("grl-flickr-%s", goa_account_get_id (account)); +-} +- +-static void +-is_online_data_cached (GObject *object, +- GAsyncResult *res, +- gpointer user_data) +-{ +- CcBackgroundGriloMiner *self; +- GError *error = NULL; +- GFileInfo *info = NULL; +- GFile *cache_file = G_FILE (object); +- GrlMedia *media; +- const gchar *uri; +- +- info = g_file_query_info_finish (cache_file, res, &error); +- if (info == NULL) +- { +- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) +- goto out; +- } +- +- self = CC_BACKGROUND_GRILO_MINER (user_data); +- +- media = g_object_get_data (G_OBJECT (cache_file), "grl-media"); +- uri = grl_media_get_url (media); +- +- if (info != NULL) +- { +- g_debug ("Ignored URL '%s' as it is already in the cache", uri); +- goto out; +- } +- +- g_signal_emit (self, signals[MEDIA_FOUND], 0, media); +- +- out: +- g_clear_object (&info); +- g_clear_error (&error); +-} +- +-static void +-searched_online_source (GrlSource *source, +- guint operation_id, +- GrlMedia *media, +- guint remaining, +- gpointer user_data, +- const GError *error) +-{ +- CcBackgroundGriloMiner *self = CC_BACKGROUND_GRILO_MINER (user_data); +- g_autoptr(GFile) cache_file = NULL; +- const gchar *uri; +- g_autofree gchar *cache_path = NULL; +- +- if (error != NULL) +- { +- const gchar *source_id; +- +- source_id = grl_source_get_id (source); +- g_warning ("Error searching %s: %s", source_id, error->message); +- grl_operation_cancel (operation_id); +- remaining = 0; +- goto out; +- } +- +- uri = grl_media_get_url (media); +- cache_path = bg_pictures_source_get_unique_path (uri); +- cache_file = g_file_new_for_path (cache_path); +- g_object_set_data_full (G_OBJECT (cache_file), "grl-media", media, g_object_unref); +- g_file_query_info_async (cache_file, +- G_FILE_ATTRIBUTE_STANDARD_TYPE, +- G_FILE_QUERY_INFO_NONE, +- G_PRIORITY_DEFAULT, +- self->cancellable, +- is_online_data_cached, +- self); +- +- out: +- if (remaining == 0) +- g_object_unref (self); +-} +- +-static void +-query_online_source (CcBackgroundGriloMiner *self, GrlSource *source) +-{ +- const GList *keys; +- GrlCaps *caps; +- GrlOperationOptions *options; +- +- keys = grl_source_supported_keys (source); +- caps = grl_source_get_caps (source, GRL_OP_BROWSE); +- options = grl_operation_options_new (caps); +- grl_operation_options_set_count (options, REMOTE_ITEM_COUNT); +- grl_operation_options_set_resolution_flags (options, GRL_RESOLVE_FAST_ONLY); +- grl_operation_options_set_type_filter (options, GRL_TYPE_FILTER_IMAGE); +- +- grl_source_search (source, NULL, keys, options, searched_online_source, g_object_ref (self)); +- g_object_unref (options); +-} +- +-static void +-add_online_source_cb (CcBackgroundGriloMiner *self, +- GrlSource *source) +-{ +- GList *l; +- gboolean found = FALSE; +- const gchar *source_id; +- +- source_id = grl_source_get_id (source); +- for (l = self->accounts; l != NULL && !found; l = l->next) +- { +- GoaObject *goa_object = GOA_OBJECT (l->data); +- g_autofree gchar *account_id = NULL; +- +- account_id = get_grilo_id (goa_object); +- if (g_strcmp0 (source_id, account_id) == 0) +- { +- query_online_source (self, source); +- found = TRUE; +- } +- } +-} +- +-static void +-client_async_ready (GObject *source, +- GAsyncResult *res, +- gpointer user_data) +-{ +- CcBackgroundGriloMiner *self; +- g_autoptr(GError) error = NULL; +- GList *accounts = NULL; +- GList *photo_accounts = NULL; +- GList *l; +- GoaClient *client = NULL; +- GrlRegistry *registry; +- +- client = goa_client_new_finish (res, &error); +- if (client == NULL) +- { +- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) +- g_warning ("Failed to create GoaClient: %s", error->message); +- goto out; +- } +- +- self = CC_BACKGROUND_GRILO_MINER (user_data); +- +- accounts = goa_client_get_accounts (client); +- for (l = accounts; l != NULL; l = l->next) +- { +- GoaObject *goa_object = GOA_OBJECT (l->data); +- GoaAccount *account; +- GoaPhotos *photos; +- const gchar *provider_type; +- +- account = goa_object_peek_account (goa_object); +- provider_type = goa_account_get_provider_type (account); +- +- photos = goa_object_peek_photos (goa_object); +- if (photos != NULL && g_strcmp0 (provider_type, "flickr") == 0) +- photo_accounts = g_list_prepend (photo_accounts, g_object_ref (goa_object)); +- } +- +- if (photo_accounts == NULL) +- goto out; +- +- registry = grl_registry_get_default (); +- +- for (l = photo_accounts; l != NULL; l = l->next) +- { +- GoaObject *goa_object = GOA_OBJECT (l->data); +- GrlSource *source; +- g_autofree gchar *account_id = NULL; +- +- account_id = get_grilo_id (goa_object); +- source = grl_registry_lookup_source (registry, account_id); +- if (source != NULL) +- query_online_source (self, source); +- } +- +- self->accounts = photo_accounts; +- photo_accounts = NULL; +- +- g_signal_connect_object (registry, "source-added", G_CALLBACK (add_online_source_cb), self, G_CONNECT_SWAPPED); +- +- out: +- g_list_free_full (photo_accounts, g_object_unref); +- g_list_free_full (accounts, g_object_unref); +- g_clear_object (&client); +-} +- +-static void +-setup_online_accounts (CcBackgroundGriloMiner *self) +-{ +- goa_client_new (self->cancellable, client_async_ready, self); +-} +- +-static void +-cc_background_grilo_miner_dispose (GObject *object) +-{ +- CcBackgroundGriloMiner *self = CC_BACKGROUND_GRILO_MINER (object); +- +- if (self->cancellable) +- { +- g_cancellable_cancel (self->cancellable); +- g_clear_object (&self->cancellable); +- } +- +- if (self->accounts) +- { +- g_list_free_full (self->accounts, g_object_unref); +- self->accounts = NULL; +- } +- +- G_OBJECT_CLASS (cc_background_grilo_miner_parent_class)->dispose (object); +-} +- +-static void +-cc_background_grilo_miner_init (CcBackgroundGriloMiner *self) +-{ +- self->cancellable = g_cancellable_new (); +-} +- +-static void +-cc_background_grilo_miner_class_init (CcBackgroundGriloMinerClass *klass) +-{ +- g_autoptr(GError) error = NULL; +- GObjectClass *object_class = G_OBJECT_CLASS (klass); +- GrlRegistry *registry; +- +- object_class->dispose = cc_background_grilo_miner_dispose; +- +- signals[MEDIA_FOUND] = g_signal_new ("media-found", +- G_TYPE_FROM_CLASS (klass), +- G_SIGNAL_RUN_LAST, +- 0, /* class_offset */ +- NULL, /* accumulator */ +- NULL, /* accu_data */ +- g_cclosure_marshal_VOID__OBJECT, +- G_TYPE_NONE, +- 1, +- GRL_TYPE_MEDIA); +- +- grl_init (NULL, NULL); +- registry = grl_registry_get_default (); +- +- error = NULL; +- if (!grl_registry_load_all_plugins (registry, FALSE, &error) || +- !grl_registry_activate_plugin_by_id (registry, "grl-flickr", &error)) +- g_warning ("%s", error->message); +-} +- +-CcBackgroundGriloMiner * +-cc_background_grilo_miner_new (void) +-{ +- return g_object_new (CC_TYPE_BACKGROUND_GRILO_MINER, NULL); +-} +- +-void +-cc_background_grilo_miner_start (CcBackgroundGriloMiner *self) +-{ +- setup_online_accounts (self); +-} +diff --git a/panels/background/cc-background-grilo-miner.h b/panels/background/cc-background-grilo-miner.h +deleted file mode 100644 +index b018129aeba5..000000000000 +--- a/panels/background/cc-background-grilo-miner.h ++++ /dev/null +@@ -1,31 +0,0 @@ +-/* +- * Copyright (C) 2014 Red Hat, Inc. +- * +- * 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 2 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 . +- */ +- +-#pragma once +- +-#include +- +-G_BEGIN_DECLS +- +-#define CC_TYPE_BACKGROUND_GRILO_MINER (cc_background_grilo_miner_get_type ()) +-G_DECLARE_FINAL_TYPE (CcBackgroundGriloMiner, cc_background_grilo_miner, CC, BACKGROUND_GRILO_MINER, GObject); +- +-CcBackgroundGriloMiner *cc_background_grilo_miner_new (void); +- +-void cc_background_grilo_miner_start (CcBackgroundGriloMiner *self); +- +-G_END_DECLS +diff --git a/panels/background/cc-background-panel.c b/panels/background/cc-background-panel.c +index 29dedf1653ad..3e50f506b514 100644 +--- a/panels/background/cc-background-panel.c ++++ b/panels/background/cc-background-panel.c +@@ -35,8 +35,6 @@ + #include "cc-background-resources.h" + #include "cc-background-xml.h" + +-#include "bg-pictures-source.h" +- + #define WP_PATH_ID "org.gnome.desktop.background" + #define WP_LOCK_PATH_ID "org.gnome.desktop.screensaver" + #define WP_URI_KEY "picture-uri" +diff --git a/panels/background/meson.build b/panels/background/meson.build +index e9fa398d4ceb..eb5e9ec84625 100644 +--- a/panels/background/meson.build ++++ b/panels/background/meson.build +@@ -76,12 +76,10 @@ common_sources += gnome.compile_resources( + + sources = common_sources + files( + 'bg-colors-source.c', +- 'bg-pictures-source.c', + 'bg-recent-source.c', + 'bg-source.c', + 'bg-wallpapers-source.c', + 'cc-background-chooser.c', +- 'cc-background-grilo-miner.c', + 'cc-background-item.c', + 'cc-background-panel.c', + 'cc-background-preview.c', +@@ -91,10 +89,8 @@ sources = common_sources + files( + deps = common_deps + [ + gdk_pixbuf_dep, + gnome_desktop_dep, +- goa_dep, + libxml_dep, + dependency('cairo-gobject'), +- dependency('grilo-0.3', version: '>= 0.3.0') + ] + + cflags += [ +-- +2.30.2 + diff --git a/SOURCES/power-profiles-backport.patch b/SOURCES/power-profiles-backport.patch new file mode 100644 index 0000000..641beff --- /dev/null +++ b/SOURCES/power-profiles-backport.patch @@ -0,0 +1,5102 @@ +From 1e3569e2f6c2c418b84ea3aa6ce5f84166ec48a7 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Wed, 31 Mar 2021 11:06:44 +0200 +Subject: [PATCH 01/33] power: Fix possible assertion on startup + +When opening the Power panel, and if power-profiles-daemon isn't already +started, we'll be autostarting it, making it send signals about all the +changed properties, including "PerformanceInhibited" even if we +don't have a Performance row. + +Ignore the property change if we don't have a row for it rather than +asserting. + + #0 0x00007f73916bc292 in raise () from /lib64/libc.so.6 + #1 0x00007f73916a58a4 in abort () from /lib64/libc.so.6 + #2 0x00007f7393203ccc in g_assertion_message.cold () from /lib64/libglib-2.0.so.0 + #3 0x00007f739325f22f in g_assertion_message_expr () from /lib64/libglib-2.0.so.0 + #4 0x000055a4099e28ae in performance_profile_set_inhibited (self=0x55a40b37e320, self=0x55a40b37e320, performance_inhibited=) at ../panels/power/cc-power-panel.c:1366 + #5 power_profiles_properties_changed_cb (self=0x55a40b37e320, changed_properties=, invalidated_properties=, proxy=) at ../panels/power/cc-power-panel.c:1425 + #6 0x00007f7393332c2f in g_closure_invoke () from /lib64/libgobject-2.0.so.0 + #7 0x00007f739334eea6 in signal_emit_unlocked_R () from /lib64/libgobject-2.0.so.0 + +1362 { +1363 CcPowerProfileRow *row; +1364 +1365 row = self->power_profiles_row[CC_POWER_PROFILE_PERFORMANCE]; +1366 g_assert (row != NULL); +1367 cc_power_profile_row_set_performance_inhibited (row, performance_inhibited); +1368 } +--- + panels/power/cc-power-panel.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index c9c4705da..0b8226a29 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -1363,7 +1363,8 @@ performance_profile_set_inhibited (CcPowerPanel *self, + CcPowerProfileRow *row; + + row = self->power_profiles_row[CC_POWER_PROFILE_PERFORMANCE]; +- g_assert (row != NULL); ++ if (!row) ++ return; + cc_power_profile_row_set_performance_inhibited (row, performance_inhibited); + } + +-- +2.32.0 + + +From da2248d43f519f13b57e89cdc80bbbfe346ad463 Mon Sep 17 00:00:00 2001 +From: Adrien Plazas +Date: Thu, 7 Jan 2021 14:00:15 +0100 +Subject: [PATCH 02/33] power: Simplifies keyboard navigation + +This drops the ability to loop through the page but significantly +simplifies the code. I think this is a good tradeoff as looping is a +rather unexpected behavior. +--- + panels/power/cc-power-panel.c | 70 ++--------------------------------- + 1 file changed, 4 insertions(+), 66 deletions(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index 0b8226a29..1d31a8a83 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -122,9 +122,6 @@ struct _CcPowerPanel + gboolean has_batteries; + char *chassis_type; + +- GList *boxes; +- GList *boxes_reverse; +- + GDBusProxy *bt_rfkill; + GDBusProxy *bt_properties; + +@@ -169,8 +166,6 @@ cc_power_panel_dispose (GObject *object) + #ifdef HAVE_NETWORK_MANAGER + g_clear_object (&self->nm_client); + #endif +- g_clear_pointer (&self->boxes, g_list_free); +- g_clear_pointer (&self->boxes_reverse, g_list_free); + if (self->iio_proxy_watch_id != 0) + g_bus_unwatch_name (self->iio_proxy_watch_id); + self->iio_proxy_watch_id = 0; +@@ -869,60 +864,12 @@ nm_client_ready_cb (GObject *source_object, + static gboolean + keynav_failed_cb (CcPowerPanel *self, GtkDirectionType direction, GtkWidget *list) + { +- GtkWidget *next_list = NULL; +- GList *item, *boxes_list; +- gdouble value, lower, upper, page; +- +- /* Find the list in the list of GtkListBoxes */ +- if (direction == GTK_DIR_DOWN) +- boxes_list = self->boxes; +- else +- boxes_list = self->boxes_reverse; +- +- item = g_list_find (boxes_list, list); +- g_assert (item); +- item = item->next; +- while (1) +- { +- if (item == NULL) +- item = boxes_list; +- +- /* Avoid looping */ +- if (item->data == list) +- break; +- +- if (gtk_widget_is_visible (item->data)) +- { +- next_list = item->data; +- break; +- } +- +- item = item->next; +- } +- +- if (next_list) +- { +- gtk_widget_child_focus (next_list, direction); +- return TRUE; +- } +- +- value = gtk_adjustment_get_value (self->focus_adjustment); +- lower = gtk_adjustment_get_lower (self->focus_adjustment); +- upper = gtk_adjustment_get_upper (self->focus_adjustment); +- page = gtk_adjustment_get_page_size (self->focus_adjustment); ++ if (direction != GTK_DIR_UP && direction != GTK_DIR_DOWN) ++ return FALSE; + +- if (direction == GTK_DIR_UP && value > lower) +- { +- gtk_adjustment_set_value (self->focus_adjustment, lower); +- return TRUE; +- } +- else if (direction == GTK_DIR_DOWN && value < upper - page) +- { +- gtk_adjustment_set_value (self->focus_adjustment, upper - page); +- return TRUE; +- } ++ direction == GTK_DIR_UP ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD; + +- return FALSE; ++ return gtk_widget_child_focus (GTK_WIDGET (self), direction); + } + + static void +@@ -1559,8 +1506,6 @@ setup_power_profiles (CcPowerPanel *self) + + gtk_widget_show (GTK_WIDGET (self->power_profile_section)); + +- self->boxes_reverse = g_list_prepend (self->boxes_reverse, self->power_profile_listbox); +- + props = g_variant_get_child_value (variant, 0); + performance_inhibited = variant_lookup_string (props, "PerformanceInhibited"); + active_profile = variant_lookup_string (props, "ActiveProfile"); +@@ -1780,7 +1725,6 @@ cc_power_panel_init (CcPowerPanel *self) + battery_label = g_markup_printf_escaped ("%s", _("Battery")); + gtk_label_set_markup (self->battery_heading, battery_label); + +- self->boxes_reverse = g_list_prepend (self->boxes_reverse, self->battery_listbox); + gtk_list_box_set_header_func (self->battery_listbox, + cc_list_box_update_header_func, + NULL, NULL); +@@ -1790,7 +1734,6 @@ cc_power_panel_init (CcPowerPanel *self) + device_label = g_markup_printf_escaped ("%s", _("Devices")); + gtk_label_set_markup (self->device_heading, device_label); + +- self->boxes_reverse = g_list_prepend (self->boxes_reverse, self->device_listbox); + gtk_list_box_set_header_func (self->device_listbox, + cc_list_box_update_header_func, + NULL, NULL); +@@ -1809,7 +1752,6 @@ cc_power_panel_init (CcPowerPanel *self) + + power_saving_label = g_strdup_printf ("%s", _("Power Saving")); + gtk_label_set_markup (self->power_saving_heading, power_saving_label); +- self->boxes_reverse = g_list_prepend (self->boxes_reverse, self->power_saving_listbox); + gtk_list_box_set_header_func (self->power_saving_listbox, + cc_list_box_update_header_func, + NULL, NULL); +@@ -1817,15 +1759,11 @@ cc_power_panel_init (CcPowerPanel *self) + + general_label = g_markup_printf_escaped ("%s", _("Suspend & Power Button")); + gtk_label_set_markup (self->general_heading, general_label); +- self->boxes_reverse = g_list_prepend (self->boxes_reverse, self->general_listbox); + gtk_list_box_set_header_func (self->general_listbox, + cc_list_box_update_header_func, + NULL, NULL); + setup_general_section (self); + +- self->boxes = g_list_copy (self->boxes_reverse); +- self->boxes = g_list_reverse (self->boxes); +- + /* populate batteries */ + g_signal_connect_object (self->up_client, "device-added", G_CALLBACK (up_client_device_added), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->up_client, "device-removed", G_CALLBACK (up_client_device_removed), self, G_CONNECT_SWAPPED); +-- +2.32.0 + + +From 25027f6c3c6928eeedb440fe2ca84412dae49ab2 Mon Sep 17 00:00:00 2001 +From: Adrien Plazas +Date: Fri, 8 Jan 2021 16:00:25 +0100 +Subject: [PATCH 03/33] power: Don't set the vadjustment + +This is useless as GtkScrolledwindow already does it on the viewport it +creates. +--- + panels/power/cc-power-panel.c | 9 --------- + panels/power/cc-power-panel.ui | 4 ++-- + 2 files changed, 2 insertions(+), 11 deletions(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index 1d31a8a83..1a8cfa969 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -91,8 +91,6 @@ struct _CcPowerPanel + GtkListBoxRow *kbd_brightness_row; + CcBrightnessScale *kbd_brightness_scale; + GtkSizeGroup *level_sizegroup; +- GtkScrolledWindow *main_scroll; +- HdyClamp *main_box; + GtkListBoxRow *mobile_row; + GtkSwitch *mobile_switch; + GtkComboBox *power_button_combo; +@@ -136,8 +134,6 @@ struct _CcPowerPanel + #ifdef HAVE_NETWORK_MANAGER + NMClient *nm_client; + #endif +- +- GtkAdjustment *focus_adjustment; + }; + + CC_PANEL_REGISTER (CcPowerPanel, cc_power_panel) +@@ -1661,8 +1657,6 @@ cc_power_panel_class_init (CcPowerPanelClass *klass) + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, kbd_brightness_row); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, kbd_brightness_scale); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, level_sizegroup); +- gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, main_scroll); +- gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, main_box); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, mobile_row); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, mobile_switch); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_button_combo); +@@ -1775,7 +1769,4 @@ cc_power_panel_init (CcPowerPanel *self) + G_CALLBACK (up_client_changed), self, G_CONNECT_SWAPPED); + } + up_client_changed (self); +- +- self->focus_adjustment = gtk_scrolled_window_get_vadjustment (self->main_scroll); +- gtk_container_set_focus_vadjustment (GTK_CONTAINER (self->main_box), self->focus_adjustment); + } +diff --git a/panels/power/cc-power-panel.ui b/panels/power/cc-power-panel.ui +index ea3cf9322..d3e200d93 100644 +--- a/panels/power/cc-power-panel.ui ++++ b/panels/power/cc-power-panel.ui +@@ -113,12 +113,12 @@ + True + False + +- ++ + True + False + never + +- ++ + True + 32 + 32 +-- +2.32.0 + + +From caf3d70a7004552e0bb382a1a1220cdac9c0c1f0 Mon Sep 17 00:00:00 2001 +From: Adrien Plazas +Date: Fri, 8 Jan 2021 16:12:56 +0100 +Subject: [PATCH 04/33] power: Use HdyComboRow for the Blank Screen row + +This simplifies the code a bit and modernizes the UI. +--- + panels/power/cc-power-panel.c | 110 ++++++++++++++++++++++++++------- + panels/power/cc-power-panel.ui | 88 ++------------------------ + 2 files changed, 92 insertions(+), 106 deletions(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index 1a8cfa969..b046b2589 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -73,7 +73,7 @@ struct _CcPowerPanel + GtkSizeGroup *battery_row_sizegroup; + GtkBox *battery_section; + GtkSizeGroup *battery_sizegroup; +- GtkListBoxRow *blank_screen_row; ++ HdyComboRow *blank_screen_row; + GtkListBoxRow *brightness_row; + CcBrightnessScale *brightness_scale; + GtkListBoxRow *bt_row; +@@ -87,7 +87,6 @@ struct _CcPowerPanel + GtkLabel *general_heading; + GtkListBox *general_listbox; + GtkBox *general_section; +- GtkComboBox *idle_delay_combo; + GtkListBoxRow *kbd_brightness_row; + CcBrightnessScale *kbd_brightness_scale; + GtkSizeGroup *level_sizegroup; +@@ -577,6 +576,49 @@ set_value_for_combo (GtkComboBox *combo_box, gint value) + gtk_combo_box_set_active_iter (combo_box, &new); + } + ++static void ++set_value_for_combo_row (HdyComboRow *combo_row, gint value) ++{ ++ gboolean insert = FALSE; ++ guint insert_before = 0; ++ guint i; ++ HdyValueObject *new; ++ GListModel *model; ++ gint value_last = 0; ++ g_autofree gchar *text = NULL; ++ ++ /* try to make the UI match the setting */ ++ model = hdy_combo_row_get_model (combo_row); ++ for (i = 0; i < g_list_model_get_n_items (model); i++) ++ { ++ HdyValueObject *value_object = g_list_model_get_item (model, i); ++ gint value_tmp = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value_object), "value")); ++ if (value_tmp == value) ++ { ++ hdy_combo_row_set_selected_index (combo_row, i); ++ return; ++ } ++ ++ /* Insert before if the next value is larger or the value is lower ++ * again (i.e. "Never" is zero and last). */ ++ if (!insert && (value_tmp > value || value_last > value_tmp)) ++ { ++ insert = TRUE; ++ insert_before = i; ++ } ++ ++ value_last = value_tmp; ++ } ++ ++ /* The value is not listed, so add it at the best point (or the end). */ ++ text = cc_util_time_to_string_text (value * 1000); ++ new = hdy_value_object_new_string (text); ++ g_object_set_data (G_OBJECT (new), "value", ++ GUINT_TO_POINTER (value)); ++ g_list_store_insert (G_LIST_STORE (model), insert_before, new); ++ hdy_combo_row_set_selected_index (combo_row, insert_before); ++} ++ + static void + set_ac_battery_ui_mode (CcPowerPanel *self) + { +@@ -869,25 +911,18 @@ keynav_failed_cb (CcPowerPanel *self, GtkDirectionType direction, GtkWidget *lis + } + + static void +-idle_delay_combo_changed_cb (CcPowerPanel *self) ++blank_screen_row_changed_cb (CcPowerPanel *self) + { +- GtkTreeIter iter; +- GtkTreeModel *model; ++ GListModel *model; ++ gint selected_index; ++ HdyValueObject *value_object; + gint value; +- gboolean ret; + +- /* no selection */ +- ret = gtk_combo_box_get_active_iter (self->idle_delay_combo, &iter); +- if (!ret) +- return; ++ model = hdy_combo_row_get_model (self->blank_screen_row); ++ selected_index = hdy_combo_row_get_selected_index (self->blank_screen_row); ++ value_object = g_list_model_get_item (model, selected_index); ++ value = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value_object), "value")); + +- /* get entry */ +- model = gtk_combo_box_get_model (self->idle_delay_combo); +- gtk_tree_model_get (model, &iter, +- 1, &value, +- -1); +- +- /* set both keys */ + g_settings_set_uint (self->session_settings, "idle-delay", value); + } + +@@ -1171,6 +1206,37 @@ has_kbd_brightness_cb (CcPowerPanel *self, + gtk_widget_set_visible (GTK_WIDGET (self->kbd_brightness_row), has_brightness); + } + ++static void ++populate_blank_screen_row (HdyComboRow *combo_row) ++{ ++ g_autoptr (GListStore) list_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT); ++ gint minutes[] = { 1, 2, 3, 4, 5, 8, 10, 12, 15 }; ++ guint i; ++ g_autoptr (HdyValueObject) never_value_object = NULL; ++ ++ for (i = 0; i < G_N_ELEMENTS (minutes); i++) ++ { ++ gchar *text = NULL; ++ g_autoptr (HdyValueObject) value_object = NULL; ++ ++ /* Translators: Option for "Blank Screen" in "Power" panel */ ++ text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d minute", "%d minutes", minutes[i]), minutes[i]); ++ value_object = hdy_value_object_new_take_string (text); ++ ++ g_object_set_data (G_OBJECT (value_object), "value", GUINT_TO_POINTER (minutes[i] * 60)); ++ g_list_store_append (list_store, value_object); ++ } ++ ++ never_value_object = hdy_value_object_new_string (C_("Idle time", "Never")); ++ g_object_set_data (G_OBJECT (never_value_object), "value", GUINT_TO_POINTER (0)); ++ g_list_store_append (list_store, never_value_object); ++ ++ hdy_combo_row_bind_name_model (combo_row, ++ G_LIST_MODEL (list_store), ++ (HdyComboRowGetNameFunc) hdy_value_object_dup_string, ++ NULL, NULL); ++} ++ + static void + setup_power_saving (CcPowerPanel *self) + { +@@ -1191,10 +1257,11 @@ setup_power_saving (CcPowerPanel *self) + self->dim_screen_switch, "active", + G_SETTINGS_BIND_DEFAULT); + ++ g_signal_handlers_block_by_func (self->blank_screen_row, blank_screen_row_changed_cb, self); ++ populate_blank_screen_row (self->blank_screen_row); + value = g_settings_get_uint (self->session_settings, "idle-delay"); +- g_signal_handlers_block_by_func (self->idle_delay_combo, idle_delay_combo_changed_cb, self); +- set_value_for_combo (self->idle_delay_combo, value); +- g_signal_handlers_unblock_by_func (self->idle_delay_combo, idle_delay_combo_changed_cb, self); ++ set_value_for_combo_row (self->blank_screen_row, value); ++ g_signal_handlers_unblock_by_func (self->blank_screen_row, blank_screen_row_changed_cb, self); + + /* The default values for these settings are unfortunate for us; + * timeout == 0, action == suspend means 'do nothing' - just +@@ -1653,7 +1720,6 @@ cc_power_panel_class_init (CcPowerPanelClass *klass) + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, general_heading); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, general_listbox); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, general_section); +- gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, idle_delay_combo); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, kbd_brightness_row); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, kbd_brightness_scale); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, level_sizegroup); +@@ -1683,7 +1749,7 @@ cc_power_panel_class_init (CcPowerPanelClass *klass) + gtk_widget_class_bind_template_callback (widget_class, bt_switch_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, has_brightness_cb); + gtk_widget_class_bind_template_callback (widget_class, has_kbd_brightness_cb); +- gtk_widget_class_bind_template_callback (widget_class, idle_delay_combo_changed_cb); ++ gtk_widget_class_bind_template_callback (widget_class, blank_screen_row_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, keynav_failed_cb); + gtk_widget_class_bind_template_callback (widget_class, mobile_switch_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, power_button_combo_changed_cb); +diff --git a/panels/power/cc-power-panel.ui b/panels/power/cc-power-panel.ui +index d3e200d93..a9bb39b77 100644 +--- a/panels/power/cc-power-panel.ui ++++ b/panels/power/cc-power-panel.ui +@@ -51,56 +51,6 @@ + + + +- +- +- +- +- +- +- +- +- +- 1 minute +- 60 +- +- +- 2 minutes +- 120 +- +- +- 3 minutes +- 180 +- +- +- 4 minutes +- 240 +- +- +- 5 minutes +- 300 +- +- +- 8 minutes +- 480 +- +- +- 10 minutes +- 600 +- +- +- 12 minutes +- 720 +- +- +- 15 minutes +- 900 +- +- +- Never +- 0 +- +- +- + + + +@@ -447,41 +397,11 @@ + + + +- ++ + True +- False +- False +- +- +- True +- 12 +- 12 +- 12 +- +- +- True +- True +- True +- end +- _Blank Screen +- 6 +- 6 +- True +- 0 +- idle_delay_combo +- +- +- +- +- True +- center +- 0 +- idle_time_liststore +- +- +- +- +- ++ _Blank Screen ++ True ++ + + + +-- +2.32.0 + + +From 7dbe681401f3f487f1e752a0569cf3d743a74187 Mon Sep 17 00:00:00 2001 +From: Adrien Plazas +Date: Fri, 8 Jan 2021 16:17:42 +0100 +Subject: [PATCH 05/33] power: Use HdyComboRow for the Power Button Behavior + row + +This simplifies the code a bit and modernizes the UI. +--- + panels/power/cc-power-panel.c | 71 ++++++++++++++++++---------------- + panels/power/cc-power-panel.ui | 46 ++-------------------- + 2 files changed, 42 insertions(+), 75 deletions(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index b046b2589..ea979cfc7 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -92,9 +92,7 @@ struct _CcPowerPanel + GtkSizeGroup *level_sizegroup; + GtkListBoxRow *mobile_row; + GtkSwitch *mobile_switch; +- GtkComboBox *power_button_combo; +- GtkListStore *power_button_liststore; +- GtkListBoxRow *power_button_row; ++ HdyComboRow *power_button_row; + GtkLabel *power_profile_heading; + GtkListBox *power_profile_listbox; + GtkBox *power_profile_section; +@@ -927,25 +925,18 @@ blank_screen_row_changed_cb (CcPowerPanel *self) + } + + static void +-power_button_combo_changed_cb (CcPowerPanel *self) ++power_button_row_changed_cb (CcPowerPanel *self) + { +- GtkTreeIter iter; +- GtkTreeModel *model; ++ GListModel *model; ++ gint selected_index; ++ HdyValueObject *value_object; + gint value; +- gboolean ret; + +- /* no selection */ +- ret = gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->power_button_combo), &iter); +- if (!ret) +- return; +- +- /* get entry */ +- model = gtk_combo_box_get_model (GTK_COMBO_BOX (self->power_button_combo)); +- gtk_tree_model_get (model, &iter, +- 1, &value, +- -1); ++ model = hdy_combo_row_get_model (self->power_button_row); ++ selected_index = hdy_combo_row_get_selected_index (self->power_button_row); ++ value_object = g_list_model_get_item (model, selected_index); ++ value = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value_object), "value")); + +- /* set both keys */ + g_settings_set_enum (self->gsd_settings, "power-button-action", value); + } + +@@ -1048,10 +1039,11 @@ set_sleep_type (const GValue *value, + } + + static void +-populate_power_button_model (GtkTreeModel *model, +- gboolean can_suspend, +- gboolean can_hibernate) ++populate_power_button_row (HdyComboRow *combo_row, ++ gboolean can_suspend, ++ gboolean can_hibernate) + { ++ g_autoptr (GListStore) list_store = NULL; + struct { + char *name; + GsdPowerButtonActionType value; +@@ -1063,20 +1055,28 @@ populate_power_button_model (GtkTreeModel *model, + }; + guint i; + ++ list_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT); + for (i = 0; i < G_N_ELEMENTS (actions); i++) + { ++ g_autoptr (HdyValueObject) value_object = NULL; ++ + if (!can_suspend && actions[i].value == GSD_POWER_BUTTON_ACTION_SUSPEND) + continue; + + if (!can_hibernate && actions[i].value == GSD_POWER_BUTTON_ACTION_HIBERNATE) + continue; + +- gtk_list_store_insert_with_values (GTK_LIST_STORE (model), +- NULL, -1, +- 0, _(actions[i].name), +- 1, actions[i].value, +- -1); ++ value_object = hdy_value_object_new_string (actions[i].name); ++ g_object_set_data (G_OBJECT (value_object), ++ "value", ++ GUINT_TO_POINTER (actions[i].value)); ++ g_list_store_append (list_store, value_object); + } ++ ++ hdy_combo_row_bind_name_model (combo_row, ++ G_LIST_MODEL (list_store), ++ (HdyComboRowGetNameFunc) hdy_value_object_dup_string, ++ NULL, NULL); + } + + #define NEVER 0 +@@ -1635,10 +1635,17 @@ setup_general_section (CcPowerPanel *self) + { + gtk_widget_show (GTK_WIDGET (self->power_button_row)); + +- populate_power_button_model (GTK_TREE_MODEL (self->power_button_liststore), can_suspend, can_hibernate); +- g_signal_handlers_block_by_func (self->power_button_combo, power_button_combo_changed_cb, self); +- set_value_for_combo (self->power_button_combo, g_settings_get_enum (self->gsd_settings, "power-button-action")); +- g_signal_handlers_unblock_by_func (self->power_button_combo, power_button_combo_changed_cb, self); ++ g_signal_handlers_block_by_func (self->power_button_row, ++ power_button_row_changed_cb, ++ self); ++ populate_power_button_row (self->power_button_row, ++ can_suspend, ++ can_hibernate); ++ set_value_for_combo_row (self->power_button_row, ++ g_settings_get_enum (self->gsd_settings, "power-button-action")); ++ g_signal_handlers_unblock_by_func (self->power_button_row, ++ power_button_row_changed_cb, ++ self); + + show_section = TRUE; + } +@@ -1725,8 +1732,6 @@ cc_power_panel_class_init (CcPowerPanelClass *klass) + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, level_sizegroup); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, mobile_row); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, mobile_switch); +- gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_button_combo); +- gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_button_liststore); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_button_row); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_heading); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_listbox); +@@ -1752,7 +1757,7 @@ cc_power_panel_class_init (CcPowerPanelClass *klass) + gtk_widget_class_bind_template_callback (widget_class, blank_screen_row_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, keynav_failed_cb); + gtk_widget_class_bind_template_callback (widget_class, mobile_switch_changed_cb); +- gtk_widget_class_bind_template_callback (widget_class, power_button_combo_changed_cb); ++ gtk_widget_class_bind_template_callback (widget_class, power_button_row_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, power_profiles_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, power_saving_listbox_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, wifi_switch_changed_cb); +diff --git a/panels/power/cc-power-panel.ui b/panels/power/cc-power-panel.ui +index a9bb39b77..18fc0a8ac 100644 +--- a/panels/power/cc-power-panel.ui ++++ b/panels/power/cc-power-panel.ui +@@ -51,14 +51,6 @@ + + + +- +- +- +- +- +- +- +- + +-- +2.32.0 + + +From 58480754efd5fdc66db0940ce5c19abfbd2158b6 Mon Sep 17 00:00:00 2001 +From: Adrien Plazas +Date: Thu, 7 Jan 2021 14:37:56 +0100 +Subject: [PATCH 08/33] power: Fix the indentation + +The indentation was purposefully left incorrect in the previous commits +to ease the review of the actual changes, this fixes it. +--- + panels/power/cc-power-panel.ui | 426 ++++++++++++++++----------------- + 1 file changed, 213 insertions(+), 213 deletions(-) + +diff --git a/panels/power/cc-power-panel.ui b/panels/power/cc-power-panel.ui +index 26d6ffddc..8e999c6d5 100644 +--- a/panels/power/cc-power-panel.ui ++++ b/panels/power/cc-power-panel.ui +@@ -57,238 +57,238 @@ + + + True ++ ++ ++ True ++ Battery ++ ++ ++ ++ ++ ++ True ++ none ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ Devices ++ ++ ++ ++ ++ ++ True ++ none ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ Power Mode ++ Affects system performance and power usage. ++ ++ ++ ++ ++ ++ True ++ none ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ Power Saving ++ ++ ++ True ++ _Screen Brightness ++ True + +- ++ + True +- Battery +- +- +- +- +- +- True +- none +- +- +- +- +- +- +- ++ center ++ True ++ screen ++ + + ++ ++ ++ ++ ++ True ++ Automatic Brightness + +- ++ + True +- Devices +- +- +- +- +- +- True +- none +- +- +- +- +- +- +- ++ center ++ + + ++ ++ ++ ++ ++ True ++ _Keyboard Brightness ++ True + +- +- False +- Power Mode +- Affects system performance and power usage. +- +- +- +- +- +- True +- none +- +- +- +- +- +- +- +- ++ ++ True ++ center ++ True ++ kbd ++ + + ++ ++ ++ ++ ++ True ++ Dim Screen When Inactive ++ dim_screen_switch + +- ++ + True +- Power Saving +- +- +- True +- _Screen Brightness +- True +- +- +- True +- center +- True +- screen +- +- +- +- +- +- +- +- True +- Automatic Brightness +- +- +- True +- center +- +- +- +- +- +- +- +- True +- _Keyboard Brightness +- True +- +- +- True +- center +- True +- kbd +- +- +- +- +- +- +- +- True +- Dim Screen When Inactive +- dim_screen_switch +- +- +- True +- center +- +- +- +- +- +- +- True +- _Blank Screen +- True +- +- +- +- +- +- False +- _Automatic Suspend +- True +- True +- +- +- +- True +- end +- +- +- +- +- +- +- +- False +- _Wi-Fi +- Wi-Fi can be turned off to save power. +- True +- wifi_switch +- +- +- True +- center +- +- +- +- +- +- +- +- False +- _Mobile Broadband +- Mobile broadband (LTE, 4G, 3G, etc.) can be turned off to save power. +- True +- mobile_switch +- +- +- True +- center +- +- +- +- +- +- +- +- False +- _Bluetooth +- Bluetooth can be turned off to save power. +- True +- bt_switch +- +- +- True +- center +- +- +- +- +- ++ center + + ++ ++ ++ ++ ++ True ++ _Blank Screen ++ True ++ ++ ++ ++ ++ ++ False ++ _Automatic Suspend ++ True ++ True ++ ++ ++ ++ True ++ end ++ ++ ++ ++ ++ ++ ++ ++ False ++ _Wi-Fi ++ Wi-Fi can be turned off to save power. ++ True ++ wifi_switch ++ ++ ++ True ++ center ++ ++ ++ ++ ++ ++ ++ ++ False ++ _Mobile Broadband ++ Mobile broadband (LTE, 4G, 3G, etc.) can be turned off to save power. ++ True ++ mobile_switch ++ ++ ++ True ++ center ++ ++ ++ ++ ++ ++ ++ ++ False ++ _Bluetooth ++ Bluetooth can be turned off to save power. ++ True ++ bt_switch + +- ++ + True +- Suspend & Power Button +- +- +- False +- Po_wer Button Behavior +- True +- +- +- +- +- +- False +- Show Battery _Percentage +- True +- battery_percentage_switch +- +- +- True +- center +- +- +- +- ++ center ++ + + ++ ++ ++ ++ ++ ++ ++ True ++ Suspend & Power Button ++ ++ ++ False ++ Po_wer Button Behavior ++ True ++ ++ ++ ++ ++ ++ False ++ Show Battery _Percentage ++ True ++ battery_percentage_switch ++ ++ ++ True ++ center ++ ++ ++ ++ ++ ++ + + + +-- +2.32.0 + + +From 036545ebc5b3deba1d4bb4ac82dc102413804e83 Mon Sep 17 00:00:00 2001 +From: Adrien Plazas +Date: Fri, 8 Jan 2021 16:29:18 +0100 +Subject: [PATCH 09/33] power: Hide the icon of the battery row when unused + +This gives more room to the label. +--- + panels/power/cc-battery-row.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/panels/power/cc-battery-row.c b/panels/power/cc-battery-row.c +index 55d7584d7..f6faca0c1 100644 +--- a/panels/power/cc-battery-row.c ++++ b/panels/power/cc-battery-row.c +@@ -275,7 +275,12 @@ cc_battery_row_new (UpDevice *device, + + /* Icon */ + if (is_kind_battery && icon_name != NULL && icon_name[0] != '\0') +- gtk_image_set_from_icon_name (self->icon, icon_name, GTK_ICON_SIZE_BUTTON); ++ { ++ gtk_image_set_from_icon_name (self->icon, icon_name, GTK_ICON_SIZE_BUTTON); ++ gtk_widget_show (GTK_WIDGET (self->icon)); ++ } ++ else ++ gtk_widget_hide (GTK_WIDGET (self->icon)); + + /* Percentage label */ + if (battery_level == UP_DEVICE_LEVEL_NONE) +@@ -347,4 +352,4 @@ UpDeviceKind + cc_battery_row_get_kind (CcBatteryRow *self) + { + return self->kind; +-} +\ No newline at end of file ++} +-- +2.32.0 + + +From c4849778de730f37618120dbdd37f30515ecc77a Mon Sep 17 00:00:00 2001 +From: Adrien Plazas +Date: Fri, 8 Jan 2021 16:30:12 +0100 +Subject: [PATCH 10/33] power: Ellipsize the labels of the battery row + +This helps the window fit any size, including the narrow one of phones. +--- + panels/power/cc-battery-row.ui | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/panels/power/cc-battery-row.ui b/panels/power/cc-battery-row.ui +index 932e5d39f..dec97a9fe 100644 +--- a/panels/power/cc-battery-row.ui ++++ b/panels/power/cc-battery-row.ui +@@ -28,6 +28,8 @@ + + + True ++ end ++ 0 + + + +@@ -79,6 +81,8 @@ + + + True ++ end ++ 0 + + + +-- +2.32.0 + + +From 33d672d634a9e1d8a2cfa74dc4d503fb6f170ea1 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 15 Jul 2021 13:08:05 +0200 +Subject: [PATCH 11/33] power: Fix keynav not working +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +gnome-control-center/panels/power/cc-power-panel.c: In function ‘keynav_failed_cb’: +gnome-control-center/panels/power/cc-power-panel.c:892:50: warning: statement with no effect [-Wunused-value] + 892 | direction == GTK_DIR_UP ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD; + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~ +--- + panels/power/cc-power-panel.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index 96558c778..d31d16d22 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -888,7 +888,7 @@ keynav_failed_cb (CcPowerPanel *self, GtkDirectionType direction, GtkWidget *lis + if (direction != GTK_DIR_UP && direction != GTK_DIR_DOWN) + return FALSE; + +- direction == GTK_DIR_UP ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD; ++ direction = GTK_DIR_UP ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD; + + return gtk_widget_child_focus (GTK_WIDGET (self), direction); + } +-- +2.32.0 + + +From 35b621ce5191461b16eefcb5e677496c0c48638c Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 15 Jul 2021 13:46:19 +0200 +Subject: [PATCH 12/33] power: Simplify emptying listbox + +We do that in a few places. +--- + panels/power/cc-power-panel.c | 22 +++++++++++++--------- + 1 file changed, 13 insertions(+), 9 deletions(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index d31d16d22..5d57afe59 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -242,25 +242,29 @@ add_device (CcPowerPanel *self, UpDevice *device) + } + + static void +-up_client_changed (CcPowerPanel *self) ++empty_listbox (GtkListBox *listbox) + { +- g_autoptr(GList) battery_children = NULL; +- g_autoptr(GList) device_children = NULL; ++ g_autoptr(GList) children = NULL; + GList *l; ++ ++ children = gtk_container_get_children (GTK_CONTAINER (listbox)); ++ for (l = children; l != NULL; l = l->next) ++ gtk_container_remove (GTK_CONTAINER (listbox), l->data); ++} ++ ++static void ++up_client_changed (CcPowerPanel *self) ++{ + gint i; + UpDeviceKind kind; + guint n_batteries; + gboolean on_ups; + g_autoptr(UpDevice) composite = NULL; + +- battery_children = gtk_container_get_children (GTK_CONTAINER (self->battery_listbox)); +- for (l = battery_children; l != NULL; l = l->next) +- gtk_container_remove (GTK_CONTAINER (self->battery_listbox), l->data); ++ empty_listbox (self->battery_listbox); + gtk_widget_hide (GTK_WIDGET (self->battery_section)); + +- device_children = gtk_container_get_children (GTK_CONTAINER (self->device_listbox)); +- for (l = device_children; l != NULL; l = l->next) +- gtk_container_remove (GTK_CONTAINER (self->device_listbox), l->data); ++ empty_listbox (self->device_listbox); + gtk_widget_hide (GTK_WIDGET (self->device_section)); + + #ifdef TEST_FAKE_DEVICES +-- +2.32.0 + + +From 7915830b4d8b691f4def35c83c06a658f5b14ac1 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 15 Jul 2021 14:39:58 +0200 +Subject: [PATCH 13/33] power: Add new power profile info row widget + +--- + panels/power/cc-power-panel.c | 1 + + panels/power/cc-power-profile-info-row.c | 67 ++++++++++ + panels/power/cc-power-profile-info-row.h | 36 ++++++ + panels/power/cc-power-profile-info-row.ui | 44 +++++++ + panels/power/icons/info-symbolic.svg | 150 ++++++++++++++++++++++ + panels/power/icons/meson.build | 5 + + panels/power/meson.build | 1 + + panels/power/power.gresource.xml | 1 + + 8 files changed, 305 insertions(+) + create mode 100644 panels/power/cc-power-profile-info-row.c + create mode 100644 panels/power/cc-power-profile-info-row.h + create mode 100644 panels/power/cc-power-profile-info-row.ui + create mode 100644 panels/power/icons/info-symbolic.svg + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index 5d57afe59..8acf62dee 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -35,6 +35,7 @@ + #include "cc-battery-row.h" + #include "cc-brightness-scale.h" + #include "cc-power-profile-row.h" ++#include "cc-power-profile-info-row.h" + #include "cc-power-panel.h" + #include "cc-power-resources.h" + #include "cc-util.h" +diff --git a/panels/power/cc-power-profile-info-row.c b/panels/power/cc-power-profile-info-row.c +new file mode 100644 +index 000000000..92bb78834 +--- /dev/null ++++ b/panels/power/cc-power-profile-info-row.c +@@ -0,0 +1,67 @@ ++/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-list-row.c ++ * ++ * Copyright 2020 Red Hat Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Bastien Nocera ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-power-profile-info-row" ++ ++#include ++ ++#include ++#include "cc-power-profile-info-row.h" ++ ++struct _CcPowerProfileInfoRow ++{ ++ GtkListBoxRow parent_instance; ++ ++ GtkLabel *title_label; ++}; ++ ++G_DEFINE_TYPE (CcPowerProfileInfoRow, cc_power_profile_info_row, GTK_TYPE_LIST_BOX_ROW) ++ ++static void ++cc_power_profile_info_row_class_init (CcPowerProfileInfoRowClass *klass) ++{ ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ ++ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/power/cc-power-profile-info-row.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, CcPowerProfileInfoRow, title_label); ++} ++ ++static void ++cc_power_profile_info_row_init (CcPowerProfileInfoRow *self) ++{ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++} ++ ++CcPowerProfileInfoRow * ++cc_power_profile_info_row_new (const char *text) ++{ ++ CcPowerProfileInfoRow *self; ++ ++ self = g_object_new (CC_TYPE_POWER_PROFILE_INFO_ROW, NULL); ++ gtk_label_set_markup (self->title_label, text); ++ ++ return self; ++} +diff --git a/panels/power/cc-power-profile-info-row.h b/panels/power/cc-power-profile-info-row.h +new file mode 100644 +index 000000000..52d055ab2 +--- /dev/null ++++ b/panels/power/cc-power-profile-info-row.h +@@ -0,0 +1,36 @@ ++/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-list-row.h ++ * ++ * Copyright 2020 Red Hat Inc ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Bastien Nocera ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++ ++G_BEGIN_DECLS ++ ++#define CC_TYPE_POWER_PROFILE_INFO_ROW (cc_power_profile_info_row_get_type()) ++G_DECLARE_FINAL_TYPE (CcPowerProfileInfoRow, cc_power_profile_info_row, CC, POWER_PROFILE_INFO_ROW, GtkListBoxRow) ++ ++CcPowerProfileInfoRow *cc_power_profile_info_row_new (const char *text); ++ ++G_END_DECLS +diff --git a/panels/power/cc-power-profile-info-row.ui b/panels/power/cc-power-profile-info-row.ui +new file mode 100644 +index 000000000..d9291ff16 +--- /dev/null ++++ b/panels/power/cc-power-profile-info-row.ui +@@ -0,0 +1,44 @@ ++ ++ ++ ++ ++ +diff --git a/panels/power/icons/info-symbolic.svg b/panels/power/icons/info-symbolic.svg +new file mode 100644 +index 000000000..502a98a50 +--- /dev/null ++++ b/panels/power/icons/info-symbolic.svg +@@ -0,0 +1,150 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/panels/power/icons/meson.build b/panels/power/icons/meson.build +index 8165371ea..c56bc65b7 100644 +--- a/panels/power/icons/meson.build ++++ b/panels/power/icons/meson.build +@@ -13,3 +13,8 @@ foreach icon_size: icon_sizes + install_dir: join_paths(control_center_icondir, 'hicolor', icon_size, 'apps') + ) + endforeach ++ ++install_data( ++ 'info-symbolic.svg', ++ install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'status') ++) +diff --git a/panels/power/meson.build b/panels/power/meson.build +index 625059dd2..af04b98ed 100644 +--- a/panels/power/meson.build ++++ b/panels/power/meson.build +@@ -22,6 +22,7 @@ sources = files( + 'cc-brightness-scale.c', + 'cc-power-panel.c', + 'cc-power-profile-row.c', ++ 'cc-power-profile-info-row.c' + ) + + sources += gnome.mkenums_simple( +diff --git a/panels/power/power.gresource.xml b/panels/power/power.gresource.xml +index 31e92b415..5a33c8e60 100644 +--- a/panels/power/power.gresource.xml ++++ b/panels/power/power.gresource.xml +@@ -4,6 +4,7 @@ + cc-battery-row.ui + cc-power-panel.ui + cc-power-profile-row.ui ++ cc-power-profile-info-row.ui + battery-levels.css + power-profiles.css + +-- +2.32.0 + + +From 1a80fda8cdbd2226a5663f0fa572dd72210722a6 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 2 Apr 2021 13:10:19 +0200 +Subject: [PATCH 14/33] power: Handle new power-profiles-daemon API + +Handle the new PerformanceDegraded property to replace +PerformanceInhibited. +--- + panels/power/cc-power-panel.c | 53 +++++++++++++++++++++++++++++++--- + panels/power/cc-power-panel.ui | 11 +++++++ + 2 files changed, 60 insertions(+), 4 deletions(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index 8acf62dee..51196a8c4 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -89,6 +89,7 @@ struct _CcPowerPanel + GtkSwitch *mobile_switch; + HdyComboRow *power_button_row; + GtkListBox *power_profile_listbox; ++ GtkListBox *power_profile_info_listbox; + HdyPreferencesGroup *power_profile_section; + GtkSizeGroup *row_sizegroup; + GtkComboBox *suspend_on_battery_delay_combo; +@@ -119,6 +120,7 @@ struct _CcPowerPanel + guint power_profiles_prop_id; + CcPowerProfileRow *power_profiles_row[NUM_CC_POWER_PROFILES]; + gboolean power_profiles_in_update; ++ gboolean has_performance_degraded; + + #ifdef HAVE_NETWORK_MANAGER + NMClient *nm_client; +@@ -1365,6 +1367,37 @@ performance_profile_set_inhibited (CcPowerPanel *self, + cc_power_profile_row_set_performance_inhibited (row, performance_inhibited); + } + ++static void ++performance_profile_set_degraded (CcPowerPanel *self) ++{ ++ g_autoptr(GVariant) variant = NULL; ++ const char *degraded, *text; ++ CcPowerProfileInfoRow *row; ++ ++ empty_listbox (self->power_profile_info_listbox); ++ gtk_widget_hide (GTK_WIDGET (self->power_profile_info_listbox)); ++ ++ variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "PerformanceDegraded"); ++ if (!variant) ++ return; ++ degraded = g_variant_get_string (variant, NULL); ++ if (*degraded == '\0') ++ return; ++ ++ gtk_widget_show (GTK_WIDGET (self->power_profile_info_listbox)); ++ ++ if (g_str_equal (degraded, "high-operating-temperature")) ++ text = _("Performance mode temporarily disabled due to high operating temperature."); ++ else if (g_str_equal (degraded, "lap-detected")) ++ text = _("Lap detected: performance mode temporarily disabled. Move the device to a stable surface to restore."); ++ else ++ text = _("Performance mode temporarily disabled."); ++ ++ row = cc_power_profile_info_row_new (text); ++ gtk_widget_show (GTK_WIDGET (row)); ++ gtk_container_add (GTK_CONTAINER (self->power_profile_info_listbox), GTK_WIDGET (row)); ++} ++ + static void + power_profiles_row_activated_cb (GtkListBox *box, + GtkListBoxRow *box_row, +@@ -1420,8 +1453,13 @@ power_profiles_properties_changed_cb (CcPowerPanel *self, + { + if (g_strcmp0 (key, "PerformanceInhibited") == 0) + { +- performance_profile_set_inhibited (self, +- g_variant_get_string (value, NULL)); ++ if (!self->has_performance_degraded) ++ performance_profile_set_inhibited (self, ++ g_variant_get_string (value, NULL)); ++ } ++ else if (g_strcmp0 (key, "PerformanceDegraded") == 0) ++ { ++ performance_profile_set_degraded (self); + } + else if (g_strcmp0 (key, "ActiveProfile") == 0) + { +@@ -1504,7 +1542,8 @@ setup_power_profiles (CcPowerPanel *self) + g_autoptr(GVariant) props = NULL; + guint i, num_children; + g_autoptr(GError) error = NULL; +- const char *performance_inhibited; ++ const char *performance_inhibited = NULL; ++ const char *performance_degraded; + const char *active_profile; + g_autoptr(GVariant) profiles = NULL; + GtkRadioButton *last_button; +@@ -1557,7 +1596,12 @@ setup_power_profiles (CcPowerPanel *self) + gtk_widget_show (GTK_WIDGET (self->power_profile_section)); + + props = g_variant_get_child_value (variant, 0); +- performance_inhibited = variant_lookup_string (props, "PerformanceInhibited"); ++ performance_degraded = variant_lookup_string (props, "PerformanceDegraded"); ++ self->has_performance_degraded = performance_degraded != NULL; ++ if (performance_degraded == NULL) ++ performance_inhibited = variant_lookup_string (props, "PerformanceInhibited"); ++ else ++ performance_profile_set_degraded (self); + active_profile = variant_lookup_string (props, "ActiveProfile"); + + last_button = NULL; +@@ -1715,6 +1759,7 @@ cc_power_panel_class_init (CcPowerPanelClass *klass) + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, mobile_switch); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_button_row); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_listbox); ++ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_info_listbox); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_section); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, row_sizegroup); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_battery_delay_combo); +diff --git a/panels/power/cc-power-panel.ui b/panels/power/cc-power-panel.ui +index 8e999c6d5..d8283c556 100644 +--- a/panels/power/cc-power-panel.ui ++++ b/panels/power/cc-power-panel.ui +@@ -123,6 +123,17 @@ + + + ++ ++ ++ False ++ none ++ 12 ++ ++ ++ ++ + + + +-- +2.32.0 + + +From 7254da88faa5cebd43db735746eb20b2b548a462 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 16 Jul 2021 10:00:12 +0200 +Subject: [PATCH 15/33] power: Prepare for adding more power profile info boxes + +Rename and re-indent the code that adds the power profile info boxes to +prepare for adding more info boxes when needed. +--- + panels/power/cc-power-panel.c | 46 ++++++++++++++++++----------------- + 1 file changed, 24 insertions(+), 22 deletions(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index 51196a8c4..1869be065 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -1368,34 +1368,35 @@ performance_profile_set_inhibited (CcPowerPanel *self, + } + + static void +-performance_profile_set_degraded (CcPowerPanel *self) ++power_profile_update_info_boxes (CcPowerPanel *self) + { +- g_autoptr(GVariant) variant = NULL; +- const char *degraded, *text; ++ g_autoptr(GVariant) degraded_variant = NULL; ++ const char *degraded = NULL; + CcPowerProfileInfoRow *row; + + empty_listbox (self->power_profile_info_listbox); + gtk_widget_hide (GTK_WIDGET (self->power_profile_info_listbox)); + +- variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "PerformanceDegraded"); +- if (!variant) +- return; +- degraded = g_variant_get_string (variant, NULL); +- if (*degraded == '\0') +- return; ++ degraded_variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "PerformanceDegraded"); ++ if (degraded_variant) ++ degraded = g_variant_get_string (degraded_variant, NULL); ++ if (degraded && *degraded != '\0') ++ { ++ const char *text; + +- gtk_widget_show (GTK_WIDGET (self->power_profile_info_listbox)); ++ gtk_widget_show (GTK_WIDGET (self->power_profile_info_listbox)); + +- if (g_str_equal (degraded, "high-operating-temperature")) +- text = _("Performance mode temporarily disabled due to high operating temperature."); +- else if (g_str_equal (degraded, "lap-detected")) +- text = _("Lap detected: performance mode temporarily disabled. Move the device to a stable surface to restore."); +- else +- text = _("Performance mode temporarily disabled."); ++ if (g_str_equal (degraded, "high-operating-temperature")) ++ text = _("Performance mode temporarily disabled due to high operating temperature."); ++ else if (g_str_equal (degraded, "lap-detected")) ++ text = _("Lap detected: performance mode temporarily disabled. Move the device to a stable surface to restore."); ++ else ++ text = _("Performance mode temporarily disabled."); + +- row = cc_power_profile_info_row_new (text); +- gtk_widget_show (GTK_WIDGET (row)); +- gtk_container_add (GTK_CONTAINER (self->power_profile_info_listbox), GTK_WIDGET (row)); ++ row = cc_power_profile_info_row_new (text); ++ gtk_widget_show (GTK_WIDGET (row)); ++ gtk_container_add (GTK_CONTAINER (self->power_profile_info_listbox), GTK_WIDGET (row)); ++ } + } + + static void +@@ -1459,7 +1460,7 @@ power_profiles_properties_changed_cb (CcPowerPanel *self, + } + else if (g_strcmp0 (key, "PerformanceDegraded") == 0) + { +- performance_profile_set_degraded (self); ++ power_profile_update_info_boxes (self); + } + else if (g_strcmp0 (key, "ActiveProfile") == 0) + { +@@ -1600,8 +1601,6 @@ setup_power_profiles (CcPowerPanel *self) + self->has_performance_degraded = performance_degraded != NULL; + if (performance_degraded == NULL) + performance_inhibited = variant_lookup_string (props, "PerformanceInhibited"); +- else +- performance_profile_set_degraded (self); + active_profile = variant_lookup_string (props, "ActiveProfile"); + + last_button = NULL; +@@ -1649,6 +1648,9 @@ setup_power_profiles (CcPowerPanel *self) + + self->power_profiles_prop_id = g_signal_connect_object (G_OBJECT (self->power_profiles_proxy), "g-properties-changed", + G_CALLBACK (power_profiles_properties_changed_cb), self, G_CONNECT_SWAPPED); ++ ++ if (self->has_performance_degraded) ++ power_profile_update_info_boxes (self); + } + + static void +-- +2.32.0 + + +From 173904b7eeea145c38065939a1dd8a408745f3c9 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 16 Jul 2021 10:07:28 +0200 +Subject: [PATCH 16/33] power: Move variant_lookup_string() helper function + +--- + panels/power/cc-power-panel.c | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index 1869be065..4633627c2 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -1343,6 +1343,18 @@ setup_power_saving (CcPowerPanel *self) + #endif + } + ++static const char * ++variant_lookup_string (GVariant *dict, ++ const char *key) ++{ ++ GVariant *variant; ++ ++ variant = g_variant_lookup_value (dict, key, G_VARIANT_TYPE_STRING); ++ if (!variant) ++ return NULL; ++ return g_variant_get_string (variant, NULL); ++} ++ + static void + performance_profile_set_active (CcPowerPanel *self, + const char *profile_str) +@@ -1427,18 +1439,6 @@ perf_profile_list_box_sort (GtkListBoxRow *row1, + return 0; + } + +-static const char * +-variant_lookup_string (GVariant *dict, +- const char *key) +-{ +- GVariant *variant; +- +- variant = g_variant_lookup_value (dict, key, G_VARIANT_TYPE_STRING); +- if (!variant) +- return NULL; +- return g_variant_get_string (variant, NULL); +-} +- + static void + power_profiles_properties_changed_cb (CcPowerPanel *self, + GVariant *changed_properties, +-- +2.32.0 + + +From f49c47787de81fa39f100f3903d2d886905cd4c7 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 16 Jul 2021 11:01:52 +0200 +Subject: [PATCH 17/33] power: Show power profile info boxes for profile holds + +Applications can request that power-profiles-daemon "hold" a particular +power profile for the duration of a task or event, such as launching a +taxing application, or saving power because of low battery. + +Show those holds in the same type of info boxes we already use to show +"degraded" performance. + +See https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/merge_requests/46 +--- + panels/power/cc-power-panel.c | 77 ++++++++++++++++++++++++++++++++++- + 1 file changed, 76 insertions(+), 1 deletion(-) + +diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c +index 4633627c2..4f1989cdf 100644 +--- a/panels/power/cc-power-panel.c ++++ b/panels/power/cc-power-panel.c +@@ -24,6 +24,7 @@ + #include + #include + #include ++#include + #include + + #ifdef HAVE_NETWORK_MANAGER +@@ -1383,12 +1384,25 @@ static void + power_profile_update_info_boxes (CcPowerPanel *self) + { + g_autoptr(GVariant) degraded_variant = NULL; ++ g_autoptr(GVariant) holds_variant = NULL; ++ g_autoptr(GVariant) profile_variant = NULL; ++ guint i, num_children; + const char *degraded = NULL; ++ const char *profile; + CcPowerProfileInfoRow *row; ++ int next_insert = 0; + + empty_listbox (self->power_profile_info_listbox); + gtk_widget_hide (GTK_WIDGET (self->power_profile_info_listbox)); + ++ profile_variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "ActiveProfile"); ++ if (!profile_variant) ++ { ++ g_warning ("No 'ActiveProfile' property on power-profiles-daemon service"); ++ return; ++ } ++ profile = g_variant_get_string (profile_variant, NULL); ++ + degraded_variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "PerformanceDegraded"); + if (degraded_variant) + degraded = g_variant_get_string (degraded_variant, NULL); +@@ -1408,6 +1422,66 @@ power_profile_update_info_boxes (CcPowerPanel *self) + row = cc_power_profile_info_row_new (text); + gtk_widget_show (GTK_WIDGET (row)); + gtk_container_add (GTK_CONTAINER (self->power_profile_info_listbox), GTK_WIDGET (row)); ++ if (g_str_equal (profile, "performance")) ++ next_insert = 1; ++ } ++ ++ holds_variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "ActiveProfileHolds"); ++ if (!holds_variant) ++ { ++ g_warning ("No 'ActiveProfileHolds' property on power-profiles-daemon service"); ++ return; ++ } ++ ++ num_children = g_variant_n_children (holds_variant); ++ for (i = 0; i < num_children; i++) ++ { ++ g_autoptr(GDesktopAppInfo) app_info = NULL; ++ g_autoptr(GVariant) hold_variant = NULL; ++ g_autofree char *text = NULL; ++ const char *app_id, *held_profile, *reason, *name; ++ ++ hold_variant = g_variant_get_child_value (holds_variant, i); ++ if (!hold_variant || !g_variant_is_of_type (hold_variant, G_VARIANT_TYPE ("a{sv}"))) ++ continue; ++ ++ app_id = variant_lookup_string (hold_variant, "ApplicationId"); ++ if (!app_id) ++ continue; ++ app_info = g_desktop_app_info_new (app_id); ++ name = app_info ? g_app_info_get_name (G_APP_INFO (app_info)) : app_id; ++ held_profile = variant_lookup_string (hold_variant, "Profile"); ++ reason = variant_lookup_string (hold_variant, "Reason"); ++ g_debug ("Adding info row for %s hold by %s: %s", held_profile, app_id, reason); ++ ++ if (g_strcmp0 (held_profile, "power-saver") == 0 && ++ g_strcmp0 (app_id, "org.gnome.SettingsDaemon.Power") == 0) ++ { ++ text = g_strdup (_("Low battery: power saver enabled. Previous mode will be restored when battery is sufficiently charged.")); ++ } ++ else ++ { ++ switch (cc_power_profile_from_str (held_profile)) ++ { ++ case CC_POWER_PROFILE_POWER_SAVER: ++ /* translators: "%s" is an application name */ ++ text = g_strdup_printf (_("Power Saver mode activated by “%s”."), name); ++ break; ++ case CC_POWER_PROFILE_PERFORMANCE: ++ /* translators: "%s" is an application name */ ++ text = g_strdup_printf (_("Performance mode activated by “%s”."), name); ++ break; ++ default: ++ g_assert_not_reached (); ++ } ++ } ++ ++ row = cc_power_profile_info_row_new (text); ++ gtk_widget_show (GTK_WIDGET (row)); ++ if (g_strcmp0 (held_profile, profile) != 0) ++ gtk_list_box_insert (GTK_LIST_BOX (self->power_profile_info_listbox), GTK_WIDGET (row), -1); ++ else ++ gtk_list_box_insert (GTK_LIST_BOX (self->power_profile_info_listbox), GTK_WIDGET (row), next_insert); + } + } + +@@ -1458,7 +1532,8 @@ power_profiles_properties_changed_cb (CcPowerPanel *self, + performance_profile_set_inhibited (self, + g_variant_get_string (value, NULL)); + } +- else if (g_strcmp0 (key, "PerformanceDegraded") == 0) ++ else if (g_strcmp0 (key, "PerformanceDegraded") == 0 || ++ g_strcmp0 (key, "ActiveProfileHolds") == 0) + { + power_profile_update_info_boxes (self); + } +-- +2.32.0 + + +From d7cabb849c425fd84e85a981bcd56a6df1d87868 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 16 Jul 2021 11:32:37 +0200 +Subject: [PATCH 18/33] power: Tweak power profile info boxes horizontal + spacing + +A bit too much whitespace around the info icon. +--- + panels/power/cc-power-profile-info-row.ui | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/panels/power/cc-power-profile-info-row.ui b/panels/power/cc-power-profile-info-row.ui +index d9291ff16..bc49a24ac 100644 +--- a/panels/power/cc-power-profile-info-row.ui ++++ b/panels/power/cc-power-profile-info-row.ui +@@ -8,16 +8,16 @@ + + True + horizontal +- 12 +- 12 ++ 8 ++ 8 + 8 + 8 +- 12 ++ 8 + + + True + 6 +- 18 ++ 6 + info-symbolic + 5 + + + +-- +2.32.0 + + +From 254a9bfa8ac8be8403d0258742f0c3f8c8db9633 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Wed, 4 Aug 2021 11:26:33 +0200 +Subject: [PATCH 30/33] power: Remove icons from power profiles + +They were originally included was to educate users about what the icons +mean, for when they appeared in the top bar. However, since we no +longer plan on showing the status icon in the top bar, it's not so +important that people learn the meaning of the icons. + +See https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/1421 +--- + panels/power/cc-power-profile-row.c | 13 +------------ + panels/power/cc-power-profile-row.ui | 15 +-------------- + 2 files changed, 2 insertions(+), 26 deletions(-) + +diff --git a/panels/power/cc-power-profile-row.c b/panels/power/cc-power-profile-row.c +index ee66bdfd0..a8458c366 100644 +--- a/panels/power/cc-power-profile-row.c ++++ b/panels/power/cc-power-profile-row.c +@@ -35,7 +35,6 @@ struct _CcPowerProfileRow + GtkListBoxRow parent_instance; + + GtkRadioButton *button; +- GtkImage *icon_image; + GtkLabel *subtitle_label; + GtkLabel *title_label; + +@@ -106,7 +105,6 @@ cc_power_profile_row_class_init (CcPowerProfileRowClass *klass) + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/power/cc-power-profile-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcPowerProfileRow, button); +- gtk_widget_class_bind_template_child (widget_class, CcPowerProfileRow, icon_image); + gtk_widget_class_bind_template_child (widget_class, CcPowerProfileRow, subtitle_label); + gtk_widget_class_bind_template_child (widget_class, CcPowerProfileRow, title_label); + +@@ -175,7 +173,7 @@ CcPowerProfileRow * + cc_power_profile_row_new (CcPowerProfile power_profile) + { + CcPowerProfileRow *self; +- const char *text, *subtext, *icon_name, *class_name; ++ const char *text, *subtext; + + self = g_object_new (CC_TYPE_POWER_PROFILE_ROW, NULL); + +@@ -185,20 +183,14 @@ cc_power_profile_row_new (CcPowerProfile power_profile) + case CC_POWER_PROFILE_PERFORMANCE: + text = _("Performance"); + subtext = _("High performance and power usage."); +- icon_name = "power-profile-performance-symbolic"; +- class_name = "performance"; + break; + case CC_POWER_PROFILE_BALANCED: + text = _("Balanced Power"); + subtext = _("Standard performance and power usage."); +- icon_name = "power-profile-balanced-symbolic"; +- class_name = NULL; + break; + case CC_POWER_PROFILE_POWER_SAVER: + text = _("Power Saver"); + subtext = _("Reduced performance and power usage."); +- icon_name = "power-profile-power-saver-symbolic"; +- class_name = "low-power"; + break; + default: + g_assert_not_reached (); +@@ -206,9 +198,6 @@ cc_power_profile_row_new (CcPowerProfile power_profile) + + gtk_label_set_markup (self->title_label, text); + gtk_label_set_markup (self->subtitle_label, subtext); +- gtk_image_set_from_icon_name (self->icon_image, icon_name, GTK_ICON_SIZE_MENU); +- if (class_name != NULL) +- gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self->icon_image)), class_name); + + return self; + } +diff --git a/panels/power/cc-power-profile-row.ui b/panels/power/cc-power-profile-row.ui +index 64516edf3..1391093ba 100644 +--- a/panels/power/cc-power-profile-row.ui ++++ b/panels/power/cc-power-profile-row.ui +@@ -28,19 +28,6 @@ + 2 + + +- +- +- True +- 6 +- +- +- +- 1 +- 0 +- +- + + + True +@@ -53,7 +40,7 @@ + 6 + + +- 2 ++ 1 + 0 + + +-- +2.32.0 + + +From 96efcd2835a46b8009b5f6524a2d37089f40dd44 Mon Sep 17 00:00:00 2001 +From: Allan Day +Date: Wed, 4 Aug 2021 11:28:14 +0200 +Subject: [PATCH 31/33] power: Align power profile info boxes with profiles + text + +See https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/1421 +--- + panels/power/cc-power-profile-info-row.ui | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/panels/power/cc-power-profile-info-row.ui b/panels/power/cc-power-profile-info-row.ui +index 6f411c688..34807c38c 100644 +--- a/panels/power/cc-power-profile-info-row.ui ++++ b/panels/power/cc-power-profile-info-row.ui +@@ -8,11 +8,11 @@ + + True + horizontal +- 8 ++ 4 + 8 + 8 + 8 +- 8 ++ 4 + + + True +-- +2.32.0 + + +From 618e269230a71ae3ac84a35daaf15ad3db38f459 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 5 Aug 2021 15:24:58 +0200 +Subject: [PATCH 32/33] power: Change "Balanced" power profile label + +https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/4530#note_1241985 +--- + panels/power/cc-power-profile-row.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/panels/power/cc-power-profile-row.c b/panels/power/cc-power-profile-row.c +index a8458c366..9d7f1fe6b 100644 +--- a/panels/power/cc-power-profile-row.c ++++ b/panels/power/cc-power-profile-row.c +@@ -185,7 +185,7 @@ cc_power_profile_row_new (CcPowerProfile power_profile) + subtext = _("High performance and power usage."); + break; + case CC_POWER_PROFILE_BALANCED: +- text = _("Balanced Power"); ++ text = _("Balanced"); + subtext = _("Standard performance and power usage."); + break; + case CC_POWER_PROFILE_POWER_SAVER: +-- +2.32.0 + + +From 98ed744dcefd8c4d8398b1489d0c583a4d5787d9 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 5 Aug 2021 15:24:58 +0200 +Subject: [PATCH 33/33] power: Update power profile labels + +Use "Balanced" instead of "Balanced Power", and make sure to add a +context for all the profiles for translators. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/4530#note_1243075 +--- + panels/power/cc-power-profile-row.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/panels/power/cc-power-profile-row.c b/panels/power/cc-power-profile-row.c +index 9d7f1fe6b..f290caa10 100644 +--- a/panels/power/cc-power-profile-row.c ++++ b/panels/power/cc-power-profile-row.c +@@ -181,15 +181,15 @@ cc_power_profile_row_new (CcPowerProfile power_profile) + switch (self->power_profile) + { + case CC_POWER_PROFILE_PERFORMANCE: +- text = _("Performance"); ++ text = C_("Power profile", "Performance"); + subtext = _("High performance and power usage."); + break; + case CC_POWER_PROFILE_BALANCED: +- text = _("Balanced"); ++ text = C_("Power profile", "Balanced"); + subtext = _("Standard performance and power usage."); + break; + case CC_POWER_PROFILE_POWER_SAVER: +- text = _("Power Saver"); ++ text = C_("Power profile", "Power Saver"); + subtext = _("Reduced performance and power usage."); + break; + default: +-- +2.32.0 + diff --git a/SOURCES/subscription-manager-support.patch b/SOURCES/subscription-manager-support.patch new file mode 100644 index 0000000..7af3186 --- /dev/null +++ b/SOURCES/subscription-manager-support.patch @@ -0,0 +1,2564 @@ +From 9849810143193393bbf6fecfca2b415a4c58e147 Mon Sep 17 00:00:00 2001 +From: Kalev Lember +Date: Fri, 28 Jun 2019 17:14:36 +0200 +Subject: [PATCH 1/2] info-overview: Add subscription manager integration + +--- + panels/info-overview/cc-info-overview-panel.c | 190 ++++++ + .../info-overview/cc-info-overview-panel.ui | 10 + + panels/info-overview/cc-subscription-common.h | 34 + + .../cc-subscription-details-dialog.c | 578 ++++++++++++++++ + .../cc-subscription-details-dialog.h | 33 + + .../cc-subscription-details-dialog.ui | 425 ++++++++++++ + .../cc-subscription-register-dialog.c | 416 ++++++++++++ + .../cc-subscription-register-dialog.h | 33 + + .../cc-subscription-register-dialog.ui | 623 ++++++++++++++++++ + .../info-overview/info-overview.gresource.xml | 2 + + panels/info-overview/meson.build | 6 +- + po/POTFILES.in | 4 + + 12 files changed, 2353 insertions(+), 1 deletion(-) + create mode 100644 panels/info-overview/cc-subscription-common.h + create mode 100644 panels/info-overview/cc-subscription-details-dialog.c + create mode 100644 panels/info-overview/cc-subscription-details-dialog.h + create mode 100644 panels/info-overview/cc-subscription-details-dialog.ui + create mode 100644 panels/info-overview/cc-subscription-register-dialog.c + create mode 100644 panels/info-overview/cc-subscription-register-dialog.h + create mode 100644 panels/info-overview/cc-subscription-register-dialog.ui + +diff --git a/panels/info-overview/cc-info-overview-panel.c b/panels/info-overview/cc-info-overview-panel.c +index b20e5c1f7..99c8b20d7 100644 +--- a/panels/info-overview/cc-info-overview-panel.c ++++ b/panels/info-overview/cc-info-overview-panel.c +@@ -26,6 +26,9 @@ + #include "cc-os-release.h" + + #include "cc-info-overview-resources.h" ++#include "cc-subscription-common.h" ++#include "cc-subscription-details-dialog.h" ++#include "cc-subscription-register-dialog.h" + #include "info-cleanup.h" + + #include +@@ -74,8 +77,13 @@ struct _CcInfoOverviewPanel + CcListRow *os_type_row; + CcListRow *processor_row; + CcListRow *software_updates_row; ++ CcListRow *subscription_row; + CcListRow *virtualization_row; + CcListRow *windowing_system_row; ++ ++ /* Subscription */ ++ GCancellable *subscription_cancellable; ++ GDBusProxy *subscription_proxy; + }; + + typedef struct +@@ -770,6 +778,156 @@ info_overview_panel_setup_overview (CcInfoOverviewPanel *self) + cc_list_row_set_secondary_markup (self->graphics_row, graphics_hardware_string); + } + ++static void ++reload_subscription_status (CcInfoOverviewPanel *self) ++{ ++ GsdSubmanSubscriptionStatus status; ++ gboolean registered; ++ gboolean updates; ++ ++ if (self->subscription_proxy == NULL) ++ { ++ gtk_widget_hide (GTK_WIDGET (self->subscription_row)); ++ return; ++ } ++ ++ if (!get_subscription_status (self->subscription_proxy, &status)) ++ { ++ gtk_widget_hide (GTK_WIDGET (self->subscription_row)); ++ return; ++ } ++ ++ switch (status) ++ { ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN: ++ registered = FALSE; ++ updates = FALSE; ++ gtk_widget_set_sensitive (GTK_WIDGET (self->software_updates_row), FALSE); ++ break; ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED: ++ registered = TRUE; ++ updates = TRUE; ++ gtk_widget_set_sensitive (GTK_WIDGET (self->software_updates_row), TRUE); ++ break; ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID: ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID: ++ registered = TRUE; ++ updates = TRUE; ++ gtk_widget_set_sensitive (GTK_WIDGET (self->software_updates_row), TRUE); ++ break; ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID: ++ registered = TRUE; ++ updates = FALSE; ++ gtk_widget_set_sensitive (GTK_WIDGET (self->software_updates_row), FALSE); ++ break; ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_NO_INSTALLED_PRODUCTS: ++ registered = FALSE; ++ updates = FALSE; ++ gtk_widget_set_sensitive (GTK_WIDGET (self->software_updates_row), FALSE); ++ break; ++ default: ++ g_assert_not_reached (); ++ break; ++ } ++ ++ if (registered) ++ { ++ if (updates) ++ cc_list_row_set_secondary_label (self->software_updates_row, _("System is registered and able to receive software updates.")); ++ else ++ cc_list_row_set_secondary_label (self->software_updates_row, _("System is registered but is unable to receive all software updates.")); ++ ++ cc_list_row_set_secondary_label (self->subscription_row, _("Registered System")); ++ g_object_set_data (G_OBJECT (self->subscription_row), "is-registered", GINT_TO_POINTER (TRUE)); ++ } ++ else ++ { ++ cc_list_row_set_secondary_label (self->software_updates_row, _("Register this system to receive software updates.")); ++ cc_list_row_set_secondary_label (self->subscription_row, _("System Not Registered")); ++ g_object_set_data (G_OBJECT (self->subscription_row), "is-registered", GINT_TO_POINTER (FALSE)); ++ } ++} ++ ++static void ++open_subscription_details_dialog (CcInfoOverviewPanel *self) ++{ ++ CcSubscriptionDetailsDialog *dialog; ++ GtkWindow *toplevel; ++ CcShell *shell; ++ ++ g_assert (CC_IS_INFO_OVERVIEW_PANEL (self)); ++ ++ dialog = cc_subscription_details_dialog_new (self->subscription_proxy, ++ self->subscription_cancellable); ++ ++ shell = cc_panel_get_shell (CC_PANEL (self)); ++ toplevel = GTK_WINDOW (cc_shell_get_toplevel (shell)); ++ gtk_window_set_transient_for (GTK_WINDOW (self->hostname_editor), toplevel); ++ ++ gtk_dialog_run (GTK_DIALOG (dialog)); ++ gtk_widget_destroy (GTK_WIDGET (dialog)); ++} ++ ++static void ++open_subscription_register_dialog (CcInfoOverviewPanel *self) ++{ ++ CcSubscriptionRegisterDialog *dialog; ++ GtkWindow *toplevel; ++ CcShell *shell; ++ ++ g_assert (CC_IS_INFO_OVERVIEW_PANEL (self)); ++ ++ dialog = cc_subscription_register_dialog_new (self->subscription_proxy, ++ self->subscription_cancellable); ++ ++ shell = cc_panel_get_shell (CC_PANEL (self)); ++ toplevel = GTK_WINDOW (cc_shell_get_toplevel (shell)); ++ gtk_window_set_transient_for (GTK_WINDOW (self->hostname_editor), toplevel); ++ ++ gtk_dialog_run (GTK_DIALOG (dialog)); ++ gtk_widget_destroy (GTK_WIDGET (dialog)); ++} ++ ++static void ++on_subscription_status_changed (GDBusProxy *proxy, ++ GVariant *changed_properties, ++ GStrv invalidated_properties, ++ CcInfoOverviewPanel *self) ++{ ++ g_cancellable_cancel (self->subscription_cancellable); ++ g_object_unref (self->subscription_cancellable); ++ ++ self->subscription_cancellable = g_cancellable_new (); ++ ++ reload_subscription_status (self); ++} ++ ++static void ++info_overview_panel_setup_subscriptions (CcInfoOverviewPanel *self) ++{ ++ g_autoptr(GError) error = NULL; ++ ++ self->subscription_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, ++ G_DBUS_PROXY_FLAGS_NONE, ++ NULL, ++ "org.gnome.SettingsDaemon.Subscription", ++ "/org/gnome/SettingsDaemon/Subscription", ++ "org.gnome.SettingsDaemon.Subscription", ++ NULL, &error); ++ if (error != NULL) ++ { ++ g_debug ("Unable to create a proxy for org.gnome.SettingsDaemon.Subscription: %s", ++ error->message); ++ reload_subscription_status (self); ++ return; ++ } ++ ++ g_signal_connect (self->subscription_proxy, "g-properties-changed", ++ G_CALLBACK (on_subscription_status_changed), self); ++ ++ reload_subscription_status (self); ++} ++ + static gboolean + does_gnome_software_exist (void) + { +@@ -860,6 +1018,15 @@ cc_info_panel_row_activated_cb (CcInfoOverviewPanel *self, + open_hostname_edit_dialog (self); + else if (row == self->software_updates_row) + open_software_update (self); ++ else if (row == self->subscription_row) ++ { ++ gboolean registered = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-registered")); ++ ++ if (registered) ++ open_subscription_details_dialog (self); ++ else ++ open_subscription_register_dialog (self); ++ } + } + + #ifdef DARK_MODE_DISTRIBUTOR_LOGO +@@ -908,11 +1075,30 @@ setup_os_logo (CcInfoOverviewPanel *panel) + #endif + } + ++static void ++cc_info_overview_panel_finalize (GObject *object) ++{ ++ CcInfoOverviewPanel *self = (CcInfoOverviewPanel *) object; ++ ++ if (self->subscription_cancellable) ++ { ++ g_cancellable_cancel (self->subscription_cancellable); ++ g_clear_object (&self->subscription_cancellable); ++ } ++ ++ g_clear_object (&self->subscription_proxy); ++ ++ G_OBJECT_CLASS (cc_info_overview_panel_parent_class)->finalize (object); ++} ++ + static void + cc_info_overview_panel_class_init (CcInfoOverviewPanelClass *klass) + { ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + ++ object_class->finalize = cc_info_overview_panel_finalize; ++ + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/info-overview/cc-info-overview-panel.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcInfoOverviewPanel, device_name_entry); +@@ -932,6 +1118,7 @@ cc_info_overview_panel_class_init (CcInfoOverviewPanelClass *klass) + gtk_widget_class_bind_template_child (widget_class, CcInfoOverviewPanel, processor_row); + gtk_widget_class_bind_template_child (widget_class, CcInfoOverviewPanel, rename_button); + gtk_widget_class_bind_template_child (widget_class, CcInfoOverviewPanel, software_updates_row); ++ gtk_widget_class_bind_template_child (widget_class, CcInfoOverviewPanel, subscription_row); + gtk_widget_class_bind_template_child (widget_class, CcInfoOverviewPanel, virtualization_row); + gtk_widget_class_bind_template_child (widget_class, CcInfoOverviewPanel, windowing_system_row); + +@@ -951,11 +1138,14 @@ cc_info_overview_panel_init (CcInfoOverviewPanel *self) + + g_resources_register (cc_info_overview_get_resource ()); + ++ self->subscription_cancellable = g_cancellable_new (); ++ + if (!does_gnome_software_exist () && !does_gpk_update_viewer_exist ()) + gtk_widget_hide (GTK_WIDGET (self->software_updates_row)); + + info_overview_panel_setup_overview (self); + info_overview_panel_setup_virt (self); ++ info_overview_panel_setup_subscriptions (self); + + setup_os_logo (self); + } +diff --git a/panels/info-overview/cc-info-overview-panel.ui b/panels/info-overview/cc-info-overview-panel.ui +index 2f5d3cf8b..ddd2cf614 100644 +--- a/panels/info-overview/cc-info-overview-panel.ui ++++ b/panels/info-overview/cc-info-overview-panel.ui +@@ -179,6 +179,16 @@ + + + ++ ++ ++ ++ True ++ Subscription ++ System Not Registered ++ go-next-symbolic ++ ++ ++ + + + +diff --git a/panels/info-overview/cc-subscription-common.h b/panels/info-overview/cc-subscription-common.h +new file mode 100644 +index 000000000..034d64181 +--- /dev/null ++++ b/panels/info-overview/cc-subscription-common.h +@@ -0,0 +1,34 @@ ++#ifndef CC_SUBSCRIPTION_COMMON_H ++#define CC_SUBSCRIPTION_COMMON_H ++ ++typedef enum { ++ GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN, ++ GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID, ++ GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID, ++ GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED, ++ GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID, ++ GSD_SUBMAN_SUBSCRIPTION_STATUS_NO_INSTALLED_PRODUCTS, ++ GSD_SUBMAN_SUBSCRIPTION_STATUS_LAST ++} GsdSubmanSubscriptionStatus; ++ ++static inline gboolean ++get_subscription_status (GDBusProxy *subscription_proxy, ++ GsdSubmanSubscriptionStatus *status) ++{ ++ g_autoptr(GVariant) status_variant = NULL; ++ guint32 u; ++ ++ status_variant = g_dbus_proxy_get_cached_property (subscription_proxy, "SubscriptionStatus"); ++ if (!status_variant) ++ { ++ g_debug ("Unable to get SubscriptionStatus property"); ++ return FALSE; ++ } ++ ++ g_variant_get (status_variant, "u", &u); ++ *status = u; ++ ++ return TRUE; ++} ++ ++#endif +diff --git a/panels/info-overview/cc-subscription-details-dialog.c b/panels/info-overview/cc-subscription-details-dialog.c +new file mode 100644 +index 000000000..15da1d911 +--- /dev/null ++++ b/panels/info-overview/cc-subscription-details-dialog.c +@@ -0,0 +1,578 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- ++ * ++ * Copyright 2019 Red Hat, Inc, ++ * ++ * 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 2 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 . ++ * ++ * Written by: Kalev Lember ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++ ++#include "cc-subscription-details-dialog.h" ++#include "cc-subscription-common.h" ++ ++#define DBUS_TIMEOUT 300000 /* 5 minutes */ ++ ++typedef enum { ++ DIALOG_STATE_SHOW_DETAILS, ++ DIALOG_STATE_SUBSCRIBE, ++ DIALOG_STATE_SUBSCRIBING, ++ DIALOG_STATE_UNREGISTER, ++ DIALOG_STATE_UNREGISTERING ++} DialogState; ++ ++struct _CcSubscriptionDetailsDialog ++{ ++ GtkDialog parent_instance; ++ ++ DialogState state; ++ GCancellable *cancellable; ++ GDBusProxy *subscription_proxy; ++ GPtrArray *products; ++ ++ /* template widgets */ ++ GtkButton *back_button; ++ GtkSpinner *spinner; ++ GtkStack *header_stack; ++ GtkButton *header_subscribe_button; ++ GtkButton *header_unregister_button; ++ GtkRevealer *notification_revealer; ++ GtkLabel *error_label; ++ GtkStack *stack; ++ GtkStack *status_stack; ++ GtkBox *products_box1; ++ GtkBox *products_box2; ++ GtkBox *products_box3; ++ GtkButton *subscribe_button; ++ GtkSeparator *separator; ++ GtkButton *unregister_button; ++}; ++ ++G_DEFINE_TYPE (CcSubscriptionDetailsDialog, cc_subscription_details_dialog, GTK_TYPE_DIALOG); ++ ++static void reload_installed_products (CcSubscriptionDetailsDialog *self); ++ ++typedef struct ++{ ++ gchar *product_name; ++ gchar *product_id; ++ gchar *version; ++ gchar *arch; ++ gchar *status; ++ gchar *starts; ++ gchar *ends; ++} ProductData; ++ ++static void ++product_data_free (ProductData *product) ++{ ++ g_free (product->product_name); ++ g_free (product->product_id); ++ g_free (product->version); ++ g_free (product->arch); ++ g_free (product->status); ++ g_free (product->starts); ++ g_free (product->ends); ++ g_free (product); ++} ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (ProductData, product_data_free); ++ ++static void ++add_product_row (GtkGrid *product_grid, const gchar *name, const gchar *value, gint top_attach) ++{ ++ GtkWidget *w; ++ ++ w = gtk_label_new (name); ++ gtk_style_context_add_class (gtk_widget_get_style_context (w), "dim-label"); ++ gtk_grid_attach (product_grid, w, 0, top_attach, 1, 1); ++ gtk_widget_set_halign (w, GTK_ALIGN_END); ++ gtk_widget_show (w); ++ ++ if (value == NULL) ++ value = _("Unknown"); ++ ++ w = gtk_label_new (value); ++ gtk_grid_attach (product_grid, w, 1, top_attach, 1, 1); ++ gtk_widget_set_halign (w, GTK_ALIGN_START); ++ gtk_widget_set_hexpand (w, TRUE); ++ gtk_widget_show (w); ++} ++ ++static GtkWidget * ++add_product (CcSubscriptionDetailsDialog *self, ProductData *product, GsdSubmanSubscriptionStatus status) ++{ ++ GtkGrid *product_grid; ++ const gchar *status_text; ++ ++ if (g_strcmp0 (product->status, "subscribed") == 0) ++ status_text = _("Subscribed"); ++ else if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED) ++ status_text = _("No Specific Subscription"); ++ else ++ status_text = _("Not Subscribed"); ++ ++ product_grid = GTK_GRID (gtk_grid_new ()); ++ gtk_grid_set_column_spacing (product_grid, 12); ++ gtk_grid_set_row_spacing (product_grid, 6); ++ gtk_widget_set_margin_top (GTK_WIDGET (product_grid), 18); ++ gtk_widget_set_margin_bottom (GTK_WIDGET (product_grid), 12); ++ gtk_widget_show (GTK_WIDGET (product_grid)); ++ ++ add_product_row (product_grid, _("Product Name"), product->product_name, 0); ++ add_product_row (product_grid, _("Product ID"), product->product_id, 1); ++ add_product_row (product_grid, _("Version"), product->version, 2); ++ add_product_row (product_grid, _("Arch"), product->arch, 3); ++ add_product_row (product_grid, _("Status"), status_text, 4); ++ ++ if (product->starts[0] != '\0' && product->ends[0] != '\0') ++ { ++ add_product_row (product_grid, _("Starts"), product->starts, 5); ++ add_product_row (product_grid, _("Ends"), product->ends, 6); ++ } ++ ++ return GTK_WIDGET (product_grid); ++} ++ ++static void ++remove_all_children (GtkContainer *container) ++{ ++ g_autoptr(GList) list = gtk_container_get_children (container); ++ ++ for (GList *l = list; l != NULL; l = l->next) ++ gtk_container_remove (container, (GtkWidget *) l->data); ++} ++ ++static void ++dialog_reload (CcSubscriptionDetailsDialog *self) ++{ ++ GtkHeaderBar *header = GTK_HEADER_BAR (gtk_dialog_get_header_bar (GTK_DIALOG (self))); ++ GsdSubmanSubscriptionStatus status = GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN; ++ ++ reload_installed_products (self); ++ ++ switch (self->state) ++ { ++ case DIALOG_STATE_SHOW_DETAILS: ++ gtk_header_bar_set_show_close_button (header, TRUE); ++ ++ gtk_window_set_title (GTK_WINDOW (self), _("Registration Details")); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->header_unregister_button), TRUE); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->header_subscribe_button), TRUE); ++ ++ gtk_widget_hide (GTK_WIDGET (self->back_button)); ++ gtk_widget_hide (GTK_WIDGET (self->header_stack)); ++ ++ gtk_stack_set_visible_child_name (self->stack, "show-details"); ++ break; ++ ++ case DIALOG_STATE_SUBSCRIBE: ++ gtk_header_bar_set_show_close_button (header, FALSE); ++ gtk_stack_set_visible_child_name (self->header_stack, "subscribe"); ++ gtk_window_set_title (GTK_WINDOW (self), _("Subscribe System")); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->header_subscribe_button), TRUE); ++ ++ gtk_widget_show (GTK_WIDGET (self->back_button)); ++ ++ gtk_stack_set_visible_child_name (self->header_stack, "subscribe"); ++ gtk_widget_show (GTK_WIDGET (self->header_stack)); ++ ++ gtk_stack_set_visible_child_name (self->stack, "subscribe"); ++ break; ++ ++ case DIALOG_STATE_SUBSCRIBING: ++ gtk_header_bar_set_show_close_button (header, FALSE); ++ gtk_window_set_title (GTK_WINDOW (self), _("Looking For Available Subscriptions…")); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->header_subscribe_button), FALSE); ++ ++ gtk_widget_show (GTK_WIDGET (self->back_button)); ++ ++ gtk_stack_set_visible_child_name (self->header_stack, "subscribe"); ++ gtk_widget_show (GTK_WIDGET (self->header_stack)); ++ ++ gtk_stack_set_visible_child_name (self->stack, "subscribe"); ++ break; ++ ++ case DIALOG_STATE_UNREGISTER: ++ gtk_header_bar_set_show_close_button (header, FALSE); ++ ++ gtk_window_set_title (GTK_WINDOW (self), _("Unregister System")); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->header_unregister_button), TRUE); ++ ++ gtk_widget_show (GTK_WIDGET (self->back_button)); ++ ++ gtk_stack_set_visible_child_name (self->header_stack, "unregister"); ++ gtk_widget_show (GTK_WIDGET (self->header_stack)); ++ ++ gtk_stack_set_visible_child_name (self->stack, "unregister"); ++ break; ++ ++ case DIALOG_STATE_UNREGISTERING: ++ gtk_header_bar_set_show_close_button (header, FALSE); ++ ++ gtk_window_set_title (GTK_WINDOW (self), _("Unregistering System…")); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->header_unregister_button), FALSE); ++ ++ gtk_widget_show (GTK_WIDGET (self->back_button)); ++ ++ gtk_stack_set_visible_child_name (self->header_stack, "unregister"); ++ gtk_widget_show (GTK_WIDGET (self->header_stack)); ++ ++ gtk_stack_set_visible_child_name (self->stack, "unregister"); ++ break; ++ ++ default: ++ g_assert_not_reached (); ++ break; ++ } ++ ++ remove_all_children (GTK_CONTAINER (self->products_box1)); ++ remove_all_children (GTK_CONTAINER (self->products_box2)); ++ remove_all_children (GTK_CONTAINER (self->products_box3)); ++ ++ if (self->products == NULL || self->products->len == 0) ++ { ++ /* the widgets are duplicate to allow sliding between two stack pages */ ++ GtkWidget *w1 = gtk_label_new (_("No installed products detected.")); ++ GtkWidget *w2 = gtk_label_new (_("No installed products detected.")); ++ GtkWidget *w3 = gtk_label_new (_("No installed products detected.")); ++ gtk_widget_show (w1); ++ gtk_widget_show (w2); ++ gtk_widget_show (w3); ++ gtk_container_add (GTK_CONTAINER (self->products_box1), w1); ++ gtk_container_add (GTK_CONTAINER (self->products_box2), w2); ++ gtk_container_add (GTK_CONTAINER (self->products_box3), w3); ++ gtk_stack_set_visible_child_name (self->status_stack, "no-installed-products"); ++ ++ gtk_widget_hide (GTK_WIDGET (self->subscribe_button)); ++ gtk_widget_hide (GTK_WIDGET (self->separator)); ++ return; ++ } ++ ++ get_subscription_status (self->subscription_proxy, &status); ++ ++ for (guint i = 0; i < self->products->len; i++) ++ { ++ ProductData *product = g_ptr_array_index (self->products, i); ++ /* the widgets are duplicate to allow sliding between two stack pages */ ++ GtkWidget *w1 = add_product (self, product, status); ++ GtkWidget *w2 = add_product (self, product, status); ++ GtkWidget *w3 = add_product (self, product, status); ++ gtk_container_add (GTK_CONTAINER (self->products_box1), w1); ++ gtk_container_add (GTK_CONTAINER (self->products_box2), w2); ++ gtk_container_add (GTK_CONTAINER (self->products_box3), w3); ++ } ++ ++ switch (status) ++ { ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID: ++ gtk_stack_set_visible_child_name (self->status_stack, "fully-subscribed"); ++ gtk_widget_hide (GTK_WIDGET (self->subscribe_button)); ++ break; ++ ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID: ++ gtk_stack_set_visible_child_name (self->status_stack, "partly-subscribed"); ++ gtk_widget_show (GTK_WIDGET (self->subscribe_button)); ++ break; ++ ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED: ++ gtk_stack_set_visible_child_name (self->status_stack, "subscription-not-needed"); ++ gtk_widget_hide (GTK_WIDGET (self->subscribe_button)); ++ break; ++ ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN: ++ default: ++ gtk_stack_set_visible_child_name (self->status_stack, "not-subscribed"); ++ gtk_widget_show (GTK_WIDGET (self->subscribe_button)); ++ break; ++ } ++ ++ gtk_widget_set_visible (GTK_WIDGET (self->separator), ++ gtk_widget_get_visible (GTK_WIDGET (self->subscribe_button))); ++ ++} ++ ++static ProductData * ++parse_product_variant (GVariant *product_variant) ++{ ++ g_autoptr(ProductData) product = g_new0 (ProductData, 1); ++ g_auto(GVariantDict) dict; ++ ++ g_variant_dict_init (&dict, product_variant); ++ ++ g_variant_dict_lookup (&dict, "product-name", "s", &product->product_name); ++ g_variant_dict_lookup (&dict, "product-id", "s", &product->product_id); ++ g_variant_dict_lookup (&dict, "version", "s", &product->version); ++ g_variant_dict_lookup (&dict, "arch", "s", &product->arch); ++ g_variant_dict_lookup (&dict, "status", "s", &product->status); ++ g_variant_dict_lookup (&dict, "starts", "s", &product->starts); ++ g_variant_dict_lookup (&dict, "ends", "s", &product->ends); ++ ++ return g_steal_pointer (&product); ++} ++ ++static void ++reload_installed_products (CcSubscriptionDetailsDialog *self) ++{ ++ GVariantIter iter_array; ++ GVariant *child; ++ g_autoptr(GError) error = NULL; ++ g_autoptr(GVariant) installed_products_variant = NULL; ++ ++ installed_products_variant = g_dbus_proxy_get_cached_property (self->subscription_proxy, "InstalledProducts"); ++ if (installed_products_variant == NULL) ++ { ++ g_debug ("Unable to get InstalledProducts dbus property"); ++ return; ++ } ++ ++ g_ptr_array_set_size (self->products, 0); ++ ++ g_variant_iter_init (&iter_array, installed_products_variant); ++ while ((child = g_variant_iter_next_value (&iter_array)) != NULL) ++ { ++ g_autoptr(GVariant) product_variant = g_steal_pointer (&child); ++ g_ptr_array_add (self->products, parse_product_variant (product_variant)); ++ } ++} ++ ++static void ++subscription_done_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ CcSubscriptionDetailsDialog *self = (CcSubscriptionDetailsDialog *) user_data; ++ g_autoptr(GVariant) results = NULL; ++ g_autoptr(GError) error = NULL; ++ ++ results = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), ++ res, ++ &error); ++ if (results == NULL) ++ { ++ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ return; ++ ++ g_dbus_error_strip_remote_error (error); ++ gtk_label_set_text (self->error_label, error->message); ++ gtk_revealer_set_reveal_child (self->notification_revealer, TRUE); ++ ++ gtk_spinner_stop (self->spinner); ++ ++ self->state = DIALOG_STATE_SUBSCRIBE; ++ dialog_reload (self); ++ return; ++ } ++ ++ gtk_spinner_stop (self->spinner); ++ ++ self->state = DIALOG_STATE_SHOW_DETAILS; ++ dialog_reload (self); ++} ++ ++static void ++header_subscribe_button_clicked_cb (CcSubscriptionDetailsDialog *self) ++{ ++ gtk_spinner_start (self->spinner); ++ ++ self->state = DIALOG_STATE_SUBSCRIBING; ++ dialog_reload (self); ++ ++ g_dbus_proxy_call (self->subscription_proxy, ++ "Attach", ++ NULL, ++ G_DBUS_CALL_FLAGS_NONE, ++ DBUS_TIMEOUT, ++ self->cancellable, ++ subscription_done_cb, ++ self); ++} ++ ++static void ++unregistration_done_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ CcSubscriptionDetailsDialog *self = (CcSubscriptionDetailsDialog *) user_data; ++ g_autoptr(GVariant) results = NULL; ++ g_autoptr(GError) error = NULL; ++ ++ results = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), ++ res, ++ &error); ++ if (results == NULL) ++ { ++ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ return; ++ ++ g_dbus_error_strip_remote_error (error); ++ gtk_label_set_text (self->error_label, error->message); ++ gtk_revealer_set_reveal_child (self->notification_revealer, TRUE); ++ ++ gtk_spinner_stop (self->spinner); ++ ++ self->state = DIALOG_STATE_UNREGISTER; ++ dialog_reload (self); ++ return; ++ } ++ ++ gtk_spinner_stop (self->spinner); ++ ++ gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT); ++} ++ ++static void ++header_unregister_button_clicked_cb (CcSubscriptionDetailsDialog *self) ++{ ++ gtk_spinner_start (self->spinner); ++ ++ self->state = DIALOG_STATE_UNREGISTERING; ++ dialog_reload (self); ++ ++ g_dbus_proxy_call (self->subscription_proxy, ++ "Unregister", ++ NULL, ++ G_DBUS_CALL_FLAGS_NONE, ++ DBUS_TIMEOUT, ++ self->cancellable, ++ unregistration_done_cb, ++ self); ++} ++ ++static void ++back_button_clicked_cb (CcSubscriptionDetailsDialog *self) ++{ ++ gtk_spinner_stop (self->spinner); ++ ++ self->state = DIALOG_STATE_SHOW_DETAILS; ++ dialog_reload (self); ++} ++ ++static void ++subscribe_button_clicked_cb (CcSubscriptionDetailsDialog *self) ++{ ++ self->state = DIALOG_STATE_SUBSCRIBE; ++ dialog_reload (self); ++} ++ ++static void ++unregister_button_clicked_cb (CcSubscriptionDetailsDialog *self) ++{ ++ self->state = DIALOG_STATE_UNREGISTER; ++ dialog_reload (self); ++} ++ ++static void ++dismiss_notification (CcSubscriptionDetailsDialog *self) ++{ ++ gtk_revealer_set_reveal_child (self->notification_revealer, FALSE); ++} ++ ++static void ++cc_subscription_details_dialog_init (CcSubscriptionDetailsDialog *self) ++{ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++ ++ self->products = g_ptr_array_new_with_free_func ((GDestroyNotify) product_data_free); ++ self->state = DIALOG_STATE_SHOW_DETAILS; ++} ++ ++static void ++cc_subscription_details_dialog_dispose (GObject *obj) ++{ ++ CcSubscriptionDetailsDialog *self = (CcSubscriptionDetailsDialog *) obj; ++ ++ g_cancellable_cancel (self->cancellable); ++ g_clear_object (&self->cancellable); ++ g_clear_object (&self->subscription_proxy); ++ ++ G_OBJECT_CLASS (cc_subscription_details_dialog_parent_class)->dispose (obj); ++} ++ ++static void ++cc_subscription_details_dialog_finalize (GObject *obj) ++{ ++ CcSubscriptionDetailsDialog *self = (CcSubscriptionDetailsDialog *) obj; ++ ++ g_clear_pointer (&self->products, g_ptr_array_unref); ++ ++ G_OBJECT_CLASS (cc_subscription_details_dialog_parent_class)->finalize (obj); ++} ++ ++static void ++cc_subscription_details_dialog_class_init (CcSubscriptionDetailsDialogClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ ++ object_class->dispose = cc_subscription_details_dialog_dispose; ++ object_class->finalize = cc_subscription_details_dialog_finalize; ++ ++ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/info-overview/cc-subscription-details-dialog.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, back_button); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, spinner); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, header_stack); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, header_subscribe_button); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, header_unregister_button); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, notification_revealer); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, error_label); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, stack); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, status_stack); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, products_box1); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, products_box2); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, products_box3); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, subscribe_button); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, separator); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, unregister_button); ++ ++ gtk_widget_class_bind_template_callback (widget_class, back_button_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, header_subscribe_button_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, header_unregister_button_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, subscribe_button_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, unregister_button_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, dismiss_notification); ++} ++ ++static void ++on_dialog_cancelled (CcSubscriptionDetailsDialog *self) ++{ ++ gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_CLOSE); ++} ++ ++CcSubscriptionDetailsDialog * ++cc_subscription_details_dialog_new (GDBusProxy *subscription_proxy, ++ GCancellable *cancellable) ++{ ++ CcSubscriptionDetailsDialog *self; ++ ++ self = g_object_new (CC_TYPE_SUBSCRIPTION_DETAILS_DIALOG, "use-header-bar", TRUE, NULL); ++ self->subscription_proxy = g_object_ref (subscription_proxy); ++ self->cancellable = g_object_ref (cancellable); ++ ++ g_signal_connect_object (G_OBJECT (self->cancellable), ++ "cancelled", ++ G_CALLBACK (on_dialog_cancelled), ++ self, ++ G_CONNECT_SWAPPED); ++ ++ dialog_reload (self); ++ ++ return self; ++} +diff --git a/panels/info-overview/cc-subscription-details-dialog.h b/panels/info-overview/cc-subscription-details-dialog.h +new file mode 100644 +index 000000000..f14dd157b +--- /dev/null ++++ b/panels/info-overview/cc-subscription-details-dialog.h +@@ -0,0 +1,33 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- ++ * ++ * Copyright 2019 Red Hat, Inc, ++ * ++ * 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 2 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 . ++ * ++ * Written by: Kalev Lember ++ */ ++ ++#pragma once ++ ++#include ++ ++G_BEGIN_DECLS ++ ++#define CC_TYPE_SUBSCRIPTION_DETAILS_DIALOG (cc_subscription_details_dialog_get_type ()) ++G_DECLARE_FINAL_TYPE (CcSubscriptionDetailsDialog, cc_subscription_details_dialog, CC, SUBSCRIPTION_DETAILS_DIALOG, GtkDialog) ++ ++CcSubscriptionDetailsDialog *cc_subscription_details_dialog_new (GDBusProxy *subscription_proxy, ++ GCancellable *cancellable); ++ ++G_END_DECLS +diff --git a/panels/info-overview/cc-subscription-details-dialog.ui b/panels/info-overview/cc-subscription-details-dialog.ui +new file mode 100644 +index 000000000..6cdfc1220 +--- /dev/null ++++ b/panels/info-overview/cc-subscription-details-dialog.ui +@@ -0,0 +1,425 @@ ++ ++ ++ ++ +diff --git a/panels/info-overview/cc-subscription-register-dialog.c b/panels/info-overview/cc-subscription-register-dialog.c +new file mode 100644 +index 000000000..6867a976b +--- /dev/null ++++ b/panels/info-overview/cc-subscription-register-dialog.c +@@ -0,0 +1,416 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- ++ * ++ * Copyright 2019 Red Hat, Inc, ++ * ++ * 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 2 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 . ++ * ++ * Written by: Kalev Lember ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++ ++#include "cc-subscription-register-dialog.h" ++ ++#define DBUS_TIMEOUT 300000 /* 5 minutes */ ++#define SERVER_URL "subscription.rhsm.redhat.com" ++ ++typedef enum { ++ DIALOG_STATE_REGISTER, ++ DIALOG_STATE_REGISTERING ++} DialogState; ++ ++static void dialog_validate (CcSubscriptionRegisterDialog *self); ++ ++struct _CcSubscriptionRegisterDialog ++{ ++ GtkDialog parent_instance; ++ ++ DialogState state; ++ GCancellable *cancellable; ++ GDBusProxy *subscription_proxy; ++ gboolean valid; ++ ++ /* template widgets */ ++ GtkSpinner *spinner; ++ GtkButton *register_button; ++ GtkRevealer *notification_revealer; ++ GtkLabel *error_label; ++ GtkRadioButton *default_url_radio; ++ GtkRadioButton *custom_url_radio; ++ GtkRadioButton *register_radio; ++ GtkRadioButton *register_with_activation_keys_radio; ++ GtkStack *stack; ++ GtkGrid *register_grid; ++ GtkGrid *register_with_activation_keys_grid; ++ GtkEntry *url_label; ++ GtkEntry *url_entry; ++ GtkEntry *login_entry; ++ GtkEntry *password_entry; ++ GtkEntry *activation_keys_entry; ++ GtkLabel *organization_label; ++ GtkEntry *organization_entry; ++ GtkEntry *organization_entry_with_activation_keys; ++}; ++ ++G_DEFINE_TYPE (CcSubscriptionRegisterDialog, cc_subscription_register_dialog, GTK_TYPE_DIALOG); ++ ++static void ++dialog_reload (CcSubscriptionRegisterDialog *self) ++{ ++ gboolean sensitive; ++ gboolean url_entry_enabled; ++ ++ switch (self->state) ++ { ++ case DIALOG_STATE_REGISTER: ++ gtk_window_set_title (GTK_WINDOW (self), _("Register System")); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->register_button), self->valid); ++ ++ sensitive = TRUE; ++ break; ++ ++ case DIALOG_STATE_REGISTERING: ++ gtk_window_set_title (GTK_WINDOW (self), _("Registering System…")); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->register_button), FALSE); ++ ++ sensitive = FALSE; ++ break; ++ ++ default: ++ g_assert_not_reached (); ++ break; ++ } ++ ++ url_entry_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->custom_url_radio)); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->url_entry), sensitive && url_entry_enabled); ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->default_url_radio), sensitive); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->custom_url_radio), sensitive); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->register_radio), sensitive); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->register_with_activation_keys_radio), sensitive); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->login_entry), sensitive); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->password_entry), sensitive); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->activation_keys_entry), sensitive); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->password_entry), sensitive); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->organization_entry), sensitive); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->organization_entry_with_activation_keys), sensitive); ++} ++ ++static void ++custom_url_radio_toggled_cb (CcSubscriptionRegisterDialog *self) ++{ ++ gboolean active; ++ ++ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->custom_url_radio)); ++ if (active) ++ { ++ gtk_widget_set_sensitive (GTK_WIDGET (self->url_entry), TRUE); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->url_label), TRUE); ++ ++ gtk_entry_set_text (self->url_entry, ""); ++ gtk_widget_grab_focus (GTK_WIDGET (self->url_entry)); ++ } ++ else ++ { ++ gtk_widget_set_sensitive (GTK_WIDGET (self->url_entry), FALSE); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->url_label), FALSE); ++ ++ gtk_entry_set_text (self->url_entry, SERVER_URL); ++ } ++ ++ dialog_validate (self); ++} ++ ++static void ++register_with_activation_keys_radio_toggled_cb (CcSubscriptionRegisterDialog *self) ++{ ++ gint active; ++ ++ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->register_with_activation_keys_radio)); ++ if (active) ++ { ++ gtk_stack_set_visible_child_name (self->stack, "register-with-activation-keys"); ++ gtk_widget_grab_focus (GTK_WIDGET (self->activation_keys_entry)); ++ } ++ else ++ { ++ gtk_stack_set_visible_child_name (self->stack, "register"); ++ gtk_widget_grab_focus (GTK_WIDGET (self->login_entry)); ++ } ++ ++ dialog_validate (self); ++} ++ ++static void ++dialog_validate (CcSubscriptionRegisterDialog *self) ++{ ++ gboolean valid_url = TRUE; ++ gboolean valid_login = TRUE; ++ gboolean valid_password = TRUE; ++ gboolean valid_activation_keys = TRUE; ++ gboolean valid_organization = TRUE; ++ ++ /* require url when custom url radio is selected */ ++ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->custom_url_radio))) ++ { ++ const gchar *url; ++ ++ url = gtk_entry_get_text (self->url_entry); ++ valid_url = url != NULL && strlen (url) != 0; ++ } ++ ++ /* activation keys radio selected */ ++ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->register_with_activation_keys_radio))) ++ { ++ const gchar *activation_keys; ++ const gchar *organization; ++ ++ /* require activation keys */ ++ activation_keys = gtk_entry_get_text (self->activation_keys_entry); ++ valid_activation_keys = activation_keys != NULL && strlen (activation_keys) != 0; ++ ++ /* organization is required when using activation keys */ ++ organization = gtk_entry_get_text (self->organization_entry_with_activation_keys); ++ valid_organization = organization != NULL && strlen (organization) != 0; ++ ++ /* username/password radio selected */ ++ } ++ else ++ { ++ const gchar *login; ++ const gchar *password; ++ ++ /* require login */ ++ login = gtk_entry_get_text (self->login_entry); ++ valid_login = login != NULL && strlen (login) != 0; ++ ++ /* require password */ ++ password = gtk_entry_get_text (self->password_entry); ++ valid_password = password != NULL && strlen (password) != 0; ++ } ++ ++ self->valid = valid_url && valid_login && valid_password && valid_activation_keys && valid_organization; ++ gtk_widget_set_sensitive (GTK_WIDGET (self->register_button), self->valid); ++} ++ ++static void ++registration_done_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ CcSubscriptionRegisterDialog *self = (CcSubscriptionRegisterDialog *) user_data; ++ g_autoptr(GVariant) results = NULL; ++ g_autoptr(GError) error = NULL; ++ ++ results = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), ++ res, ++ &error); ++ if (results == NULL) ++ { ++ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ return; ++ ++ g_dbus_error_strip_remote_error (error); ++ gtk_label_set_text (self->error_label, error->message); ++ gtk_revealer_set_reveal_child (self->notification_revealer, TRUE); ++ ++ gtk_spinner_stop (self->spinner); ++ ++ self->state = DIALOG_STATE_REGISTER; ++ dialog_reload (self); ++ return; ++ } ++ ++ gtk_spinner_stop (self->spinner); ++ gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT); ++ return; ++} ++ ++static void ++subscription_register_with_activation_keys (CcSubscriptionRegisterDialog *self) ++{ ++ g_autoptr(GVariantBuilder) options_builder = NULL; ++ const gchar *hostname; ++ const gchar *organization; ++ const gchar *activation_keys; ++ ++ hostname = gtk_entry_get_text (self->url_entry); ++ organization = gtk_entry_get_text (self->organization_entry_with_activation_keys); ++ activation_keys = gtk_entry_get_text (self->activation_keys_entry); ++ ++ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); ++ g_variant_builder_add (options_builder, "{sv}", "kind", g_variant_new_string ("key")); ++ g_variant_builder_add (options_builder, "{sv}", "hostname", g_variant_new_string (hostname)); ++ g_variant_builder_add (options_builder, "{sv}", "organisation", g_variant_new_string (organization)); ++ g_variant_builder_add (options_builder, "{sv}", "activation-key", g_variant_new_string (activation_keys)); ++ ++ g_dbus_proxy_call (self->subscription_proxy, ++ "Register", ++ g_variant_new ("(a{sv})", ++ options_builder), ++ G_DBUS_CALL_FLAGS_NONE, ++ DBUS_TIMEOUT, ++ self->cancellable, ++ registration_done_cb, ++ self); ++} ++ ++static void ++subscription_register_with_username (CcSubscriptionRegisterDialog *self) ++{ ++ g_autoptr(GVariantBuilder) options_builder = NULL; ++ const gchar *hostname; ++ const gchar *organization; ++ const gchar *username; ++ const gchar *password; ++ ++ hostname = gtk_entry_get_text (self->url_entry); ++ organization = gtk_entry_get_text (self->organization_entry); ++ username = gtk_entry_get_text (self->login_entry); ++ password = gtk_entry_get_text (self->password_entry); ++ ++ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); ++ g_variant_builder_add (options_builder, "{sv}", "kind", g_variant_new_string ("username")); ++ g_variant_builder_add (options_builder, "{sv}", "hostname", g_variant_new_string (hostname)); ++ g_variant_builder_add (options_builder, "{sv}", "organisation", g_variant_new_string (organization)); ++ g_variant_builder_add (options_builder, "{sv}", "username", g_variant_new_string (username)); ++ g_variant_builder_add (options_builder, "{sv}", "password", g_variant_new_string (password)); ++ ++ g_dbus_proxy_call (self->subscription_proxy, ++ "Register", ++ g_variant_new ("(a{sv})", options_builder), ++ G_DBUS_CALL_FLAGS_NONE, ++ DBUS_TIMEOUT, ++ self->cancellable, ++ registration_done_cb, ++ self); ++} ++ ++static void ++register_button_clicked_cb (CcSubscriptionRegisterDialog *self) ++{ ++ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->register_with_activation_keys_radio))) ++ subscription_register_with_activation_keys (self); ++ else ++ subscription_register_with_username (self); ++ ++ gtk_spinner_start (self->spinner); ++ ++ self->state = DIALOG_STATE_REGISTERING; ++ dialog_reload (self); ++} ++ ++static void ++dismiss_notification (CcSubscriptionRegisterDialog *self) ++{ ++ gtk_revealer_set_reveal_child (self->notification_revealer, FALSE); ++} ++ ++static void ++cc_subscription_register_dialog_init (CcSubscriptionRegisterDialog *self) ++{ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++ ++ self->state = DIALOG_STATE_REGISTER; ++ ++ gtk_entry_set_text (self->url_entry, SERVER_URL); ++ gtk_widget_grab_focus (GTK_WIDGET (self->login_entry)); ++ dialog_validate (self); ++ dialog_reload (self); ++} ++ ++static void ++cc_subscription_register_dialog_dispose (GObject *obj) ++{ ++ CcSubscriptionRegisterDialog *self = (CcSubscriptionRegisterDialog *) obj; ++ ++ g_cancellable_cancel (self->cancellable); ++ g_clear_object (&self->cancellable); ++ g_clear_object (&self->subscription_proxy); ++ ++ G_OBJECT_CLASS (cc_subscription_register_dialog_parent_class)->dispose (obj); ++} ++ ++static void ++cc_subscription_register_dialog_finalize (GObject *obj) ++{ ++ G_OBJECT_CLASS (cc_subscription_register_dialog_parent_class)->finalize (obj); ++} ++ ++static void ++cc_subscription_register_dialog_class_init (CcSubscriptionRegisterDialogClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ ++ object_class->dispose = cc_subscription_register_dialog_dispose; ++ object_class->finalize = cc_subscription_register_dialog_finalize; ++ ++ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/info-overview/cc-subscription-register-dialog.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, spinner); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, register_button); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, notification_revealer); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, error_label); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, default_url_radio); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, custom_url_radio); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, register_radio); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, register_with_activation_keys_radio); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, stack); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, register_grid); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, register_with_activation_keys_grid); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, url_label); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, url_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, login_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, password_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, activation_keys_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, organization_label); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, organization_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionRegisterDialog, organization_entry_with_activation_keys); ++ ++ gtk_widget_class_bind_template_callback (widget_class, dialog_validate); ++ gtk_widget_class_bind_template_callback (widget_class, register_button_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, dismiss_notification); ++ gtk_widget_class_bind_template_callback (widget_class, custom_url_radio_toggled_cb); ++ gtk_widget_class_bind_template_callback (widget_class, register_with_activation_keys_radio_toggled_cb); ++} ++ ++static void ++on_dialog_cancelled (CcSubscriptionRegisterDialog *self) ++{ ++ gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_CLOSE); ++} ++ ++CcSubscriptionRegisterDialog * ++cc_subscription_register_dialog_new (GDBusProxy *subscription_proxy, ++ GCancellable *cancellable) ++{ ++ CcSubscriptionRegisterDialog *self; ++ ++ self = g_object_new (CC_TYPE_SUBSCRIPTION_REGISTER_DIALOG, "use-header-bar", TRUE, NULL); ++ self->subscription_proxy = g_object_ref (subscription_proxy); ++ self->cancellable = g_object_ref (cancellable); ++ ++ g_signal_connect_object (G_OBJECT (self->cancellable), ++ "cancelled", ++ G_CALLBACK (on_dialog_cancelled), ++ self, ++ G_CONNECT_SWAPPED); ++ ++ return self; ++} +diff --git a/panels/info-overview/cc-subscription-register-dialog.h b/panels/info-overview/cc-subscription-register-dialog.h +new file mode 100644 +index 000000000..31c254084 +--- /dev/null ++++ b/panels/info-overview/cc-subscription-register-dialog.h +@@ -0,0 +1,33 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- ++ * ++ * Copyright 2019 Red Hat, Inc, ++ * ++ * 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 2 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 . ++ * ++ * Written by: Kalev Lember ++ */ ++ ++#pragma once ++ ++#include ++ ++G_BEGIN_DECLS ++ ++#define CC_TYPE_SUBSCRIPTION_REGISTER_DIALOG (cc_subscription_register_dialog_get_type ()) ++G_DECLARE_FINAL_TYPE (CcSubscriptionRegisterDialog, cc_subscription_register_dialog, CC, SUBSCRIPTION_REGISTER_DIALOG, GtkDialog) ++ ++CcSubscriptionRegisterDialog *cc_subscription_register_dialog_new (GDBusProxy *subscription_proxy, ++ GCancellable *cancellable); ++ ++G_END_DECLS +diff --git a/panels/info-overview/cc-subscription-register-dialog.ui b/panels/info-overview/cc-subscription-register-dialog.ui +new file mode 100644 +index 000000000..21e317b41 +--- /dev/null ++++ b/panels/info-overview/cc-subscription-register-dialog.ui +@@ -0,0 +1,623 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ horizontal ++ ++ ++ ++ ++ ++ +diff --git a/panels/info-overview/info-overview.gresource.xml b/panels/info-overview/info-overview.gresource.xml +index 83806e0ad..e68944931 100644 +--- a/panels/info-overview/info-overview.gresource.xml ++++ b/panels/info-overview/info-overview.gresource.xml +@@ -2,6 +2,8 @@ + + + cc-info-overview-panel.ui ++ cc-subscription-details-dialog.ui ++ cc-subscription-register-dialog.ui + GnomeLogoVerticalMedium.svg + + +diff --git a/panels/info-overview/meson.build b/panels/info-overview/meson.build +index 14663f24c..1c9adb35c 100644 +--- a/panels/info-overview/meson.build ++++ b/panels/info-overview/meson.build +@@ -24,12 +24,16 @@ cflags += [ + + sources = files( + 'cc-info-overview-panel.c', ++ 'cc-subscription-details-dialog.c', ++ 'cc-subscription-register-dialog.c', + 'info-cleanup.c' + ) + + resource_data = files( + 'GnomeLogoVerticalMedium.svg', +- 'cc-info-overview-panel.ui' ++ 'cc-info-overview-panel.ui', ++ 'cc-subscription-details-dialog.ui', ++ 'cc-subscription-register-dialog.ui' + ) + + sources += gnome.compile_resources( +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 5f7e8d49a..6063b50dd 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -50,6 +50,10 @@ panels/display/cc-night-light-page.ui + panels/display/gnome-display-panel.desktop.in.in + panels/info-overview/cc-info-overview-panel.c + panels/info-overview/cc-info-overview-panel.ui ++panels/info-overview/cc-subscription-details-dialog.c ++panels/info-overview/cc-subscription-details-dialog.ui ++panels/info-overview/cc-subscription-register-dialog.c ++panels/info-overview/cc-subscription-register-dialog.ui + panels/info-overview/gnome-info-overview-panel.desktop.in.in + panels/keyboard/00-multimedia.xml.in + panels/keyboard/01-input-sources.xml.in +-- +2.31.1 + + +From 785dae8f28a08f492da1053b55a601cdcec9233e Mon Sep 17 00:00:00 2001 +From: Kalev Lember +Date: Tue, 7 Sep 2021 13:13:08 +0200 +Subject: [PATCH 2/2] info-overview: Add desktop file keywords for subscription + support + +--- + panels/info-overview/gnome-info-overview-panel.desktop.in.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/panels/info-overview/gnome-info-overview-panel.desktop.in.in b/panels/info-overview/gnome-info-overview-panel.desktop.in.in +index 06abbdc19..2bfff8483 100644 +--- a/panels/info-overview/gnome-info-overview-panel.desktop.in.in ++++ b/panels/info-overview/gnome-info-overview-panel.desktop.in.in +@@ -19,4 +19,4 @@ X-GNOME-Bugzilla-Version=@VERSION@ + # The list MUST also end with a semicolon! + # "Preferred Applications" is the old name for the preference, so make + # sure that you use the same "translation" for those keywords +-Keywords=device;system;information;hostname;memory;processor;version;default;application;preferred;cd;dvd;usb;audio;video;disc;removable;media;autorun; ++Keywords=device;system;information;hostname;memory;processor;version;default;application;preferred;cd;dvd;usb;audio;video;disc;removable;media;autorun;register;registration;subscribe;subscription; +-- +2.31.1 + diff --git a/SOURCES/wwan-backport-gnome-40.patch b/SOURCES/wwan-backport-gnome-40.patch new file mode 100644 index 0000000..408607a --- /dev/null +++ b/SOURCES/wwan-backport-gnome-40.patch @@ -0,0 +1,9200 @@ +From aa65caa35ae1c69b8b6644c1a72eb8110500e7e0 Mon Sep 17 00:00:00 2001 +From: Mohammed Sadiq +Date: Fri, 4 Oct 2019 12:27:26 +0530 +Subject: [PATCH 1/7] common: Add polkit rules for modem management + +--- + panels/common/gnome-control-center.rules.in | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/panels/common/gnome-control-center.rules.in b/panels/common/gnome-control-center.rules.in +index 971ffac63..22cf785e0 100644 +--- a/panels/common/gnome-control-center.rules.in ++++ b/panels/common/gnome-control-center.rules.in +@@ -1,6 +1,7 @@ + polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.locale1.set-locale" || + action.id == "org.freedesktop.locale1.set-keyboard" || ++ action.id == "org.freedesktop.ModemManager1.Device.Control" || + action.id == "org.freedesktop.hostname1.set-static-hostname" || + action.id == "org.freedesktop.hostname1.set-hostname" || + action.id == "org.gnome.controlcenter.datetime.configure") && +-- +2.32.0 + + +From 2b863f51831bc09d3ef56d16858724191ac9095c Mon Sep 17 00:00:00 2001 +From: Mohammed Sadiq +Date: Fri, 4 Oct 2019 22:16:23 +0530 +Subject: [PATCH 2/7] wwan: Add new panel for modem management + +The panel supports 2G/3G/4G GSM/LTE modems. CDMA2000 Modems are not supported. +If a supported modem is present, the panel will be shown and the modem will be +handled, else, network-panel shall manage the modem as it did in the past. + +If more than one modem with data enabled is present, the user is allowed to set +priority of one SIM over the other (the priority is for SIM, not modem). + +Fixes https://gitlab.gnome.org/GNOME/gnome-control-center/issues/132 +--- + meson.build | 4 + + panels/meson.build | 3 +- + panels/wwan/cc-wwan-apn-dialog.c | 424 ++++++ + panels/wwan/cc-wwan-apn-dialog.h | 40 + + panels/wwan/cc-wwan-apn-dialog.ui | 249 ++++ + panels/wwan/cc-wwan-data.c | 1446 ++++++++++++++++++++ + panels/wwan/cc-wwan-data.h | 93 ++ + panels/wwan/cc-wwan-details-dialog.c | 257 ++++ + panels/wwan/cc-wwan-details-dialog.h | 40 + + panels/wwan/cc-wwan-details-dialog.ui | 320 +++++ + panels/wwan/cc-wwan-device-page.c | 634 +++++++++ + panels/wwan/cc-wwan-device-page.h | 42 + + panels/wwan/cc-wwan-device-page.ui | 270 ++++ + panels/wwan/cc-wwan-device.c | 1355 ++++++++++++++++++ + panels/wwan/cc-wwan-device.h | 152 ++ + panels/wwan/cc-wwan-errors-private.h | 104 ++ + panels/wwan/cc-wwan-mode-dialog.c | 327 +++++ + panels/wwan/cc-wwan-mode-dialog.h | 40 + + panels/wwan/cc-wwan-mode-dialog.ui | 57 + + panels/wwan/cc-wwan-network-dialog.c | 443 ++++++ + panels/wwan/cc-wwan-network-dialog.h | 40 + + panels/wwan/cc-wwan-network-dialog.ui | 188 +++ + panels/wwan/cc-wwan-panel.c | 929 +++++++++++++ + panels/wwan/cc-wwan-panel.h | 36 + + panels/wwan/cc-wwan-panel.ui | 336 +++++ + panels/wwan/cc-wwan-sim-lock-dialog.c | 310 +++++ + panels/wwan/cc-wwan-sim-lock-dialog.h | 40 + + panels/wwan/cc-wwan-sim-lock-dialog.ui | 306 +++++ + panels/wwan/gnome-wwan-panel.desktop.in.in | 16 + + panels/wwan/meson.build | 61 + + panels/wwan/wwan.gresource.xml | 12 + + shell/cc-panel-list.c | 1 + + shell/cc-panel-loader.c | 9 + + 33 files changed, 8583 insertions(+), 1 deletion(-) + create mode 100644 panels/wwan/cc-wwan-apn-dialog.c + create mode 100644 panels/wwan/cc-wwan-apn-dialog.h + create mode 100644 panels/wwan/cc-wwan-apn-dialog.ui + create mode 100644 panels/wwan/cc-wwan-data.c + create mode 100644 panels/wwan/cc-wwan-data.h + create mode 100644 panels/wwan/cc-wwan-details-dialog.c + create mode 100644 panels/wwan/cc-wwan-details-dialog.h + create mode 100644 panels/wwan/cc-wwan-details-dialog.ui + create mode 100644 panels/wwan/cc-wwan-device-page.c + create mode 100644 panels/wwan/cc-wwan-device-page.h + create mode 100644 panels/wwan/cc-wwan-device-page.ui + create mode 100644 panels/wwan/cc-wwan-device.c + create mode 100644 panels/wwan/cc-wwan-device.h + create mode 100644 panels/wwan/cc-wwan-errors-private.h + create mode 100644 panels/wwan/cc-wwan-mode-dialog.c + create mode 100644 panels/wwan/cc-wwan-mode-dialog.h + create mode 100644 panels/wwan/cc-wwan-mode-dialog.ui + create mode 100644 panels/wwan/cc-wwan-network-dialog.c + create mode 100644 panels/wwan/cc-wwan-network-dialog.h + create mode 100644 panels/wwan/cc-wwan-network-dialog.ui + create mode 100644 panels/wwan/cc-wwan-panel.c + create mode 100644 panels/wwan/cc-wwan-panel.h + create mode 100644 panels/wwan/cc-wwan-panel.ui + create mode 100644 panels/wwan/cc-wwan-sim-lock-dialog.c + create mode 100644 panels/wwan/cc-wwan-sim-lock-dialog.h + create mode 100644 panels/wwan/cc-wwan-sim-lock-dialog.ui + create mode 100644 panels/wwan/gnome-wwan-panel.desktop.in.in + create mode 100644 panels/wwan/meson.build + create mode 100644 panels/wwan/wwan.gresource.xml + +diff --git a/meson.build b/meson.build +index 900216962..75487603b 100644 +--- a/meson.build ++++ b/meson.build +@@ -228,6 +228,10 @@ config_h.set('BUILD_NETWORK', host_is_linux, + description: 'Define to 1 to build the Network panel') + config_h.set('HAVE_NETWORK_MANAGER', host_is_linux, + description: 'Define to 1 if NetworkManager is available') ++config_h.set('BUILD_WWAN', host_is_linux, ++ description: 'Define to 1 to build the WWan panel') ++config_h.set('HAVE_WWAN', host_is_linux, ++ description: 'Define to 1 if WWan is available') + + if host_is_linux_not_s390 + # gnome-bluetooth +diff --git a/panels/meson.build b/panels/meson.build +index 2f4fdc5e3..9b9fc34f4 100644 +--- a/panels/meson.build ++++ b/panels/meson.build +@@ -26,7 +26,8 @@ panels = [ + 'sound', + 'universal-access', + 'usage', +- 'user-accounts' ++ 'user-accounts', ++ 'wwan', + ] + + if host_is_linux +diff --git a/panels/wwan/cc-wwan-apn-dialog.c b/panels/wwan/cc-wwan-apn-dialog.c +new file mode 100644 +index 000000000..bc5fde283 +--- /dev/null ++++ b/panels/wwan/cc-wwan-apn-dialog.c +@@ -0,0 +1,424 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-apn-dialog.c ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-apn-dialog" ++ ++#include ++#include ++#include ++ ++#include "cc-wwan-device.h" ++#include "cc-wwan-data.h" ++#include "list-box-helper.h" ++#include "cc-wwan-apn-dialog.h" ++#include "cc-wwan-resources.h" ++ ++/** ++ * @short_description: Dialog to manage Internet Access Points ++ */ ++ ++struct _CcWwanApnDialog ++{ ++ GtkDialog parent_instance; ++ ++ GtkButton *add_button; ++ GtkButton *back_button; ++ GtkButton *save_button; ++ GtkEntry *apn_entry; ++ GtkEntry *name_entry; ++ GtkEntry *password_entry; ++ GtkEntry *username_entry; ++ GtkGrid *apn_edit_view; ++ GtkListBox *apn_list; ++ GtkRadioButton *apn_radio_button; ++ GtkScrolledWindow *apn_list_view; ++ GtkStack *apn_settings_stack; ++ ++ CcWwanData *wwan_data; ++ CcWwanDataApn *apn_to_save; /* The APN currently being edited */ ++ CcWwanDevice *device; ++ ++ gboolean enable_data; ++ gboolean enable_roaming; ++}; ++ ++G_DEFINE_TYPE (CcWwanApnDialog, cc_wwan_apn_dialog, GTK_TYPE_DIALOG) ++ ++ ++enum { ++ PROP_0, ++ PROP_DEVICE, ++ N_PROPS ++}; ++ ++static GParamSpec *properties[N_PROPS]; ++ ++#define CC_TYPE_WWAN_APN_ROW (cc_wwan_apn_row_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanApnRow, cc_wwan_apn_row, CC, WWAN_APN_ROW, GtkListBoxRow) ++ ++struct _CcWwanApnRow ++{ ++ GtkListBoxRow parent_instance; ++ GtkRadioButton *radio_button; ++ CcWwanDataApn *apn; ++}; ++ ++G_DEFINE_TYPE (CcWwanApnRow, cc_wwan_apn_row, GTK_TYPE_LIST_BOX_ROW) ++ ++static void ++cc_wwan_apn_row_finalize (GObject *object) ++{ ++ CcWwanApnRow *row = (CcWwanApnRow *)object; ++ ++ g_clear_object (&row->apn); ++ ++ G_OBJECT_CLASS (cc_wwan_apn_row_parent_class)->finalize (object); ++} ++ ++static void ++cc_wwan_apn_row_class_init (CcWwanApnRowClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = cc_wwan_apn_row_finalize; ++} ++ ++static void ++cc_wwan_apn_row_init (CcWwanApnRow *row) ++{ ++} ++ ++static void ++cc_wwan_apn_back_clicked_cb (CcWwanApnDialog *self) ++{ ++ GtkWidget *view; ++ ++ view = gtk_stack_get_visible_child (self->apn_settings_stack); ++ ++ if (view == GTK_WIDGET (self->apn_edit_view)) ++ { ++ gtk_widget_hide (GTK_WIDGET (self->save_button)); ++ gtk_widget_show (GTK_WIDGET (self->add_button)); ++ gtk_stack_set_visible_child (self->apn_settings_stack, ++ GTK_WIDGET (self->apn_list_view)); ++ } ++ else ++ { ++ gtk_widget_hide (GTK_WIDGET (self)); ++ } ++} ++ ++static void ++cc_wwan_apn_add_clicked_cb (CcWwanApnDialog *self) ++{ ++ gtk_entry_set_text (self->name_entry, ""); ++ gtk_entry_set_text (self->apn_entry, ""); ++ gtk_entry_set_text (self->username_entry, ""); ++ gtk_entry_set_text (self->password_entry, ""); ++ ++ gtk_widget_hide (GTK_WIDGET (self->add_button)); ++ gtk_widget_show (GTK_WIDGET (self->save_button)); ++ self->apn_to_save = NULL; ++ gtk_stack_set_visible_child (self->apn_settings_stack, ++ GTK_WIDGET (self->apn_edit_view)); ++} ++ ++static void ++cc_wwan_apn_save_clicked_cb (CcWwanApnDialog *self) ++{ ++ const gchar *name, *apn_name; ++ CcWwanDataApn *apn; ++ ++ apn = self->apn_to_save; ++ self->apn_to_save = NULL; ++ ++ name = gtk_entry_get_text (self->name_entry); ++ apn_name = gtk_entry_get_text (self->apn_entry); ++ ++ if (!apn) ++ apn = cc_wwan_data_apn_new (); ++ ++ cc_wwan_data_apn_set_name (apn, name); ++ cc_wwan_data_apn_set_apn (apn, apn_name); ++ cc_wwan_data_apn_set_username (apn, gtk_entry_get_text (self->username_entry)); ++ cc_wwan_data_apn_set_password (apn, gtk_entry_get_text (self->password_entry)); ++ ++ cc_wwan_data_save_apn (self->wwan_data, apn, NULL, NULL, NULL); ++ ++ gtk_widget_hide (GTK_WIDGET (self->save_button)); ++ gtk_stack_set_visible_child (self->apn_settings_stack, ++ GTK_WIDGET (self->apn_list_view)); ++} ++ ++static void ++cc_wwan_apn_entry_changed_cb (CcWwanApnDialog *self) ++{ ++ GtkWidget *widget; ++ const gchar *str; ++ gboolean valid_name, valid_apn; ++ ++ widget = GTK_WIDGET (self->name_entry); ++ str = gtk_entry_get_text (self->name_entry); ++ valid_name = str && *str; ++ ++ if (valid_name) ++ gtk_style_context_remove_class (gtk_widget_get_style_context (widget), "error"); ++ else ++ gtk_style_context_add_class (gtk_widget_get_style_context (widget), "error"); ++ ++ widget = GTK_WIDGET (self->apn_entry); ++ str = gtk_entry_get_text (self->apn_entry); ++ valid_apn = str && *str; ++ ++ if (valid_apn) ++ gtk_style_context_remove_class (gtk_widget_get_style_context (widget), "error"); ++ else ++ gtk_style_context_add_class (gtk_widget_get_style_context (widget), "error"); ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->save_button), valid_name && valid_apn); ++} ++ ++static void ++cc_wwan_apn_activated_cb (CcWwanApnDialog *self, ++ CcWwanApnRow *row) ++{ ++ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row->radio_button), TRUE); ++} ++ ++static void ++cc_wwan_apn_changed_cb (CcWwanApnDialog *self, ++ GtkWidget *widget) ++{ ++ CcWwanApnRow *row; ++ ++ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) ++ return; ++ ++ widget = gtk_widget_get_ancestor (widget, CC_TYPE_WWAN_APN_ROW); ++ row = CC_WWAN_APN_ROW (widget); ++ ++ if (cc_wwan_data_set_default_apn (self->wwan_data, row->apn)) ++ cc_wwan_data_save_settings (self->wwan_data, NULL, NULL, NULL); ++} ++ ++static void ++cc_wwan_apn_edit_clicked_cb (CcWwanApnDialog *self, ++ GtkButton *button) ++{ ++ CcWwanDataApn *apn; ++ CcWwanApnRow *row; ++ GtkWidget *widget; ++ ++ widget = gtk_widget_get_ancestor (GTK_WIDGET (button), CC_TYPE_WWAN_APN_ROW); ++ row = CC_WWAN_APN_ROW (widget); ++ apn = row->apn; ++ self->apn_to_save = apn; ++ ++ gtk_widget_show (GTK_WIDGET (self->save_button)); ++ gtk_widget_hide (GTK_WIDGET (self->add_button)); ++ ++ gtk_entry_set_text (self->name_entry, cc_wwan_data_apn_get_name (apn)); ++ gtk_entry_set_text (self->apn_entry, cc_wwan_data_apn_get_apn (apn)); ++ gtk_entry_set_text (self->username_entry, cc_wwan_data_apn_get_username (apn)); ++ gtk_entry_set_text (self->password_entry, cc_wwan_data_apn_get_password (apn)); ++ ++ gtk_stack_set_visible_child (self->apn_settings_stack, ++ GTK_WIDGET (self->apn_edit_view)); ++} ++ ++static GtkWidget * ++cc_wwan_apn_dialog_row_new (CcWwanDataApn *apn, ++ CcWwanApnDialog *self) ++{ ++ CcWwanApnRow *row; ++ GtkWidget *grid, *name_label, *apn_label, *radio, *edit_button; ++ GtkStyleContext *context; ++ ++ row = g_object_new (CC_TYPE_WWAN_APN_ROW, NULL); ++ ++ grid = g_object_new (GTK_TYPE_GRID, ++ "margin-top", 6, ++ "margin-bottom", 6, ++ "margin-start", 6, ++ "margin-end", 6, ++ NULL); ++ ++ radio = gtk_radio_button_new_from_widget (self->apn_radio_button); ++ row->radio_button = GTK_RADIO_BUTTON (radio); ++ gtk_widget_set_margin_end (radio, 12); ++ gtk_grid_attach (GTK_GRID (grid), radio, 0, 0, 1, 2); ++ row->apn = g_object_ref (apn); ++ ++ if (cc_wwan_data_get_default_apn (self->wwan_data) == apn) ++ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE); ++ g_signal_connect_object (radio, "toggled", ++ G_CALLBACK (cc_wwan_apn_changed_cb), ++ self, G_CONNECT_SWAPPED); ++ ++ name_label = gtk_label_new (cc_wwan_data_apn_get_name (apn)); ++ gtk_widget_set_halign (name_label, GTK_ALIGN_START); ++ gtk_widget_set_hexpand (name_label, TRUE); ++ gtk_grid_attach (GTK_GRID (grid), name_label, 1, 0, 1, 1); ++ ++ apn_label = gtk_label_new (cc_wwan_data_apn_get_apn (apn)); ++ gtk_widget_set_halign (apn_label, GTK_ALIGN_START); ++ context = gtk_widget_get_style_context (apn_label); ++ gtk_style_context_add_class (context, "dim-label"); ++ gtk_grid_attach (GTK_GRID (grid), apn_label, 1, 1, 1, 1); ++ ++ edit_button = gtk_button_new_from_icon_name ("emblem-system-symbolic", ++ GTK_ICON_SIZE_BUTTON); ++ g_signal_connect_object (edit_button, "clicked", ++ G_CALLBACK (cc_wwan_apn_edit_clicked_cb), ++ self, G_CONNECT_SWAPPED); ++ gtk_grid_attach (GTK_GRID (grid), edit_button, 2, 0, 1, 2); ++ ++ gtk_container_add (GTK_CONTAINER (row), grid); ++ gtk_widget_show_all (GTK_WIDGET (row)); ++ ++ return GTK_WIDGET (row); ++} ++ ++static void ++cc_wwan_apn_dialog_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ CcWwanApnDialog *self = (CcWwanApnDialog *)object; ++ ++ switch (prop_id) ++ { ++ case PROP_DEVICE: ++ self->device = g_value_dup_object (value); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++cc_wwan_apn_dialog_constructed (GObject *object) ++{ ++ CcWwanApnDialog *self = (CcWwanApnDialog *)object; ++ ++ G_OBJECT_CLASS (cc_wwan_apn_dialog_parent_class)->constructed (object); ++ ++ self->wwan_data = cc_wwan_device_get_data (self->device); ++ ++ gtk_list_box_bind_model (self->apn_list, ++ cc_wwan_data_get_apn_list (self->wwan_data), ++ (GtkListBoxCreateWidgetFunc)cc_wwan_apn_dialog_row_new, ++ self, NULL); ++} ++ ++static void ++cc_wwan_apn_dialog_dispose (GObject *object) ++{ ++ CcWwanApnDialog *self = (CcWwanApnDialog *)object; ++ ++ g_clear_object (&self->device); ++ ++ G_OBJECT_CLASS (cc_wwan_apn_dialog_parent_class)->dispose (object); ++} ++ ++ ++static void ++cc_wwan_apn_dialog_show (GtkWidget *widget) ++{ ++ CcWwanApnDialog *self = (CcWwanApnDialog *)widget; ++ ++ gtk_widget_show (GTK_WIDGET (self->add_button)); ++ gtk_widget_hide (GTK_WIDGET (self->save_button)); ++ gtk_stack_set_visible_child (self->apn_settings_stack, ++ GTK_WIDGET (self->apn_list_view)); ++ ++ GTK_WIDGET_CLASS (cc_wwan_apn_dialog_parent_class)->show (widget); ++} ++ ++static void ++cc_wwan_apn_dialog_class_init (CcWwanApnDialogClass *klass) ++{ ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->set_property = cc_wwan_apn_dialog_set_property; ++ object_class->constructed = cc_wwan_apn_dialog_constructed; ++ object_class->dispose = cc_wwan_apn_dialog_dispose; ++ ++ widget_class->show = cc_wwan_apn_dialog_show; ++ ++ properties[PROP_DEVICE] = ++ g_param_spec_object ("device", ++ "Device", ++ "The WWAN Device", ++ CC_TYPE_WWAN_DEVICE, ++ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++ ++ gtk_widget_class_set_template_from_resource (widget_class, ++ "/org/gnome/control-center/wwan/cc-wwan-apn-dialog.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, add_button); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_edit_view); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_list); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_list_view); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_radio_button); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_settings_stack); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, back_button); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, name_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, password_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, save_button); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, username_entry); ++ ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_back_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_add_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_save_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_entry_changed_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_activated_cb); ++} ++ ++static void ++cc_wwan_apn_dialog_init (CcWwanApnDialog *self) ++{ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++} ++ ++CcWwanApnDialog * ++cc_wwan_apn_dialog_new (GtkWindow *parent_window, ++ CcWwanDevice *device) ++{ ++ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL); ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL); ++ ++ return g_object_new (CC_TYPE_WWAN_APN_DIALOG, ++ "transient-for", parent_window, ++ "use-header-bar", 1, ++ "device", device, ++ NULL); ++} +diff --git a/panels/wwan/cc-wwan-apn-dialog.h b/panels/wwan/cc-wwan-apn-dialog.h +new file mode 100644 +index 000000000..0e9885836 +--- /dev/null ++++ b/panels/wwan/cc-wwan-apn-dialog.h +@@ -0,0 +1,40 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-apn-dialog.h ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#include "cc-wwan-device.h" ++ ++G_BEGIN_DECLS ++ ++#define CC_TYPE_WWAN_APN_DIALOG (cc_wwan_apn_dialog_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanApnDialog, cc_wwan_apn_dialog, CC, WWAN_APN_DIALOG, GtkDialog) ++ ++CcWwanApnDialog *cc_wwan_apn_dialog_new (GtkWindow *parent_window, ++ CcWwanDevice *device); ++ ++G_END_DECLS +diff --git a/panels/wwan/cc-wwan-apn-dialog.ui b/panels/wwan/cc-wwan-apn-dialog.ui +new file mode 100644 +index 000000000..fb8432bc6 +--- /dev/null ++++ b/panels/wwan/cc-wwan-apn-dialog.ui +@@ -0,0 +1,249 @@ ++ ++ ++ ++ ++ ++ ++ +diff --git a/panels/wwan/cc-wwan-data.c b/panels/wwan/cc-wwan-data.c +new file mode 100644 +index 000000000..0be8f3403 +--- /dev/null ++++ b/panels/wwan/cc-wwan-data.c +@@ -0,0 +1,1446 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-data.c ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-data" ++ ++#ifdef HAVE_CONFIG_H ++# include ++#endif ++ ++#define _GNU_SOURCE ++#include ++#include ++#include ++ ++#include "cc-wwan-data.h" ++ ++/** ++ * @short_description: Device Internet Data Object ++ * @include: "cc-wwan-device-data.h" ++ * ++ * #CcWwanData represents the data object of the given ++ * #CcWwanDevice. Please note that while #CcWWanDevice ++ * is bound to the hardware device, #CcWwanData may also ++ * depend on the inserted SIM (if supported). So the state ++ * of #CcWwanData changes when SIM is changed. ++ */ ++ ++/* Priority for connections. larger the number, lower the priority */ ++#define CC_WWAN_DNS_PRIORITY_LOW (20) ++#define CC_WWAN_DNS_PRIORITY_HIGH (15) ++ ++/* These are to be set as route metric */ ++#define CC_WWAN_ROUTE_PRIORITY_LOW (1050) ++#define CC_WWAN_ROUTE_PRIORITY_HIGH (1040) ++ ++struct _CcWwanData ++{ ++ GObject parent_instance; ++ ++ MMObject *mm_object; ++ MMModem *modem; ++ MMSim *sim; ++ gchar *sim_id; ++ ++ gchar *operator_code; /* MCCMNC */ ++ GError *error; ++ ++ NMClient *nm_client; ++ NMDevice *nm_device; ++ NMAMobileProvidersDatabase *apn_db; ++ NMAMobileProvider *apn_provider; ++ CcWwanDataApn *default_apn; ++ CcWwanDataApn *old_default_apn; ++ GListStore *apn_list; ++ NMActiveConnection *active_connection; ++ ++ gint priority; ++ gboolean data_enabled; /* autoconnect enabled */ ++ gboolean home_only; /* Data roaming */ ++}; ++ ++G_DEFINE_TYPE (CcWwanData, cc_wwan_data, G_TYPE_OBJECT) ++ ++/* ++ * Default Access Point Settings Logic: ++ * For a provided SIM, all the APNs available from NetworkManager ++ * that matches the given SIM identifier (ICCID, available via ++ * mm_sim_get_identifier() or similar gdbus API) is loaded for ++ * the Device (In NetworkManager, it is saved as ‘sim-id’, if ++ * present). At a time, only one connection will be bound to ++ * a device. If there are more than one match, the item with ++ * the highest ‘route-metric’ is taken. If more matches are ++ * still available, the first item is chosen. ++ * ++ * Populating All available APNs: ++ * All Possible APNs for the given sim are populated the following ++ * way (A list of all the following avoiding duplicates) ++ * 1. The above mentioned “Default Access Point Settings Logic” ++ * 2. Get All saved Network Manager connections with the ++ * provided MCCMNC of the given SIM ++ * 3. Get All possible APNs for the MCCMNC from mobile-provider-info ++ * ++ * Testing if data is enabled: ++ * Check if any of the items from step 1 have ‘autoconnect’ set ++ * ++ * Checking/Setting current SIM for data (in case of multiple SIM): ++ * Since other networks (like wifi, ethernet) should have higher ++ * priorities we use a negative number for priority. ++ * 1. All APNs by default have priority CC_WWAN_APN_PRIORITY_LOW ++ * 2. APN of selected SIM for active data have priority of ++ * CC_WWAN_APN_PRIORITY_HIGH ++ * ++ * XXX: Since users may create custom APNs via nmtui or like tools ++ * we may have to check if there are some inconsistencies with APNs ++ * available in NetworkManager, and ask user if they have to reset ++ * the APNs that have invalid settings (basically, we care only APNs ++ * that are set to have ‘autoconnect’ enabled, and all we need is to ++ * disable autoconnect). We won’t interfere CDMA/EVDO networks. ++ */ ++struct _CcWwanDataApn { ++ GObject parent_instance; ++ ++ /* Set if the APN is from the mobile-provider-info database */ ++ NMAMobileAccessMethod *access_method; ++ ++ /* Set if the APN is saved in NetworkManager */ ++ NMConnection *nm_connection; ++ NMRemoteConnection *remote_connection; ++ ++ gboolean modified; ++}; ++ ++G_DEFINE_TYPE (CcWwanDataApn, cc_wwan_data_apn, G_TYPE_OBJECT) ++ ++enum { ++ PROP_0, ++ PROP_ERROR, ++ PROP_ENABLED, ++ N_PROPS ++}; ++ ++static GParamSpec *properties[N_PROPS]; ++ ++static void ++wwan_data_apn_reset (CcWwanDataApn *apn) ++{ ++ if (!apn) ++ return; ++ ++ g_clear_object (&apn->nm_connection); ++ g_clear_object (&apn->remote_connection); ++} ++ ++static NMConnection * ++wwan_data_get_nm_connection (CcWwanDataApn *apn) ++{ ++ NMConnection *connection; ++ NMSetting *setting; ++ g_autofree gchar *uuid = NULL; ++ ++ if (apn->nm_connection) ++ return apn->nm_connection; ++ ++ if (apn->remote_connection) ++ return NM_CONNECTION (apn->remote_connection); ++ ++ connection = nm_simple_connection_new (); ++ apn->nm_connection = connection; ++ ++ setting = nm_setting_connection_new (); ++ uuid = nm_utils_uuid_generate (); ++ g_object_set (setting, ++ NM_SETTING_CONNECTION_UUID, uuid, ++ NM_SETTING_CONNECTION_TYPE, NM_SETTING_GSM_SETTING_NAME, ++ NULL); ++ nm_connection_add_setting (connection, setting); ++ ++ setting = nm_setting_serial_new (); ++ nm_connection_add_setting (connection, setting); ++ ++ setting = nm_setting_ip4_config_new (); ++ g_object_set (setting, NM_SETTING_IP_CONFIG_METHOD, "auto", NULL); ++ nm_connection_add_setting (connection, setting); ++ ++ nm_connection_add_setting (connection, nm_setting_gsm_new ()); ++ nm_connection_add_setting (connection, nm_setting_ppp_new ()); ++ ++ return apn->nm_connection; ++} ++ ++static gboolean ++wwan_data_apn_are_same (NMRemoteConnection *remote_connection, ++ NMAMobileAccessMethod *access_method) ++{ ++ NMConnection *connection; ++ NMSetting *setting; ++ ++ if (!remote_connection) ++ return FALSE; ++ ++ connection = NM_CONNECTION (remote_connection); ++ setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); ++ ++ if (g_strcmp0 (nma_mobile_access_method_get_3gpp_apn (access_method), ++ nm_setting_gsm_get_apn (NM_SETTING_GSM (setting))) != 0) ++ return FALSE; ++ ++ if (g_strcmp0 (nma_mobile_access_method_get_username (access_method), ++ nm_setting_gsm_get_username (NM_SETTING_GSM (setting))) != 0) ++ return FALSE; ++ ++ if (g_strcmp0 (nma_mobile_access_method_get_password (access_method), ++ nm_setting_gsm_get_password (NM_SETTING_GSM (setting))) != 0) ++ return FALSE; ++ ++ return TRUE; ++} ++ ++static CcWwanDataApn * ++wwan_data_find_matching_apn (CcWwanData *self, ++ NMAMobileAccessMethod *access_method) ++{ ++ CcWwanDataApn *apn; ++ guint i, n_items; ++ ++ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->apn_list)); ++ ++ for (i = 0; i < n_items; i++) ++ { ++ apn = g_list_model_get_item (G_LIST_MODEL (self->apn_list), i); ++ ++ if (apn->access_method == access_method) ++ return apn; ++ ++ if (wwan_data_apn_are_same (apn->remote_connection, ++ access_method)) ++ return apn; ++ ++ g_object_unref (apn); ++ } ++ ++ return NULL; ++} ++ ++static gboolean ++wwan_data_nma_method_is_mms (NMAMobileAccessMethod *method) ++{ ++ const char *str; ++ ++ str = nma_mobile_access_method_get_3gpp_apn (method); ++ if (str && strcasestr (str, "mms")) ++ return TRUE; ++ ++ str = nma_mobile_access_method_get_name (method); ++ if (str && strcasestr (str, "mms")) ++ return TRUE; ++ ++ return FALSE; ++} ++ ++static void ++wwan_data_update_apn_list_db (CcWwanData *self) ++{ ++ GSList *apn_methods = NULL, *l; ++ g_autoptr(GError) error = NULL; ++ guint i = 0; ++ ++ if (!self->sim || !self->operator_code) ++ return; ++ ++ if (!self->apn_list) ++ self->apn_list = g_list_store_new (CC_TYPE_WWAN_DATA_APN); ++ ++ if (!self->apn_db) ++ self->apn_db = nma_mobile_providers_database_new_sync (NULL, NULL, NULL, &error); ++ ++ if (error) ++ { ++ g_warning ("%s", error->message); ++ return; ++ } ++ ++ if (!self->apn_provider) ++ self->apn_provider = nma_mobile_providers_database_lookup_3gpp_mcc_mnc (self->apn_db, ++ self->operator_code); ++ ++ if (self->apn_provider) ++ apn_methods = nma_mobile_provider_get_methods (self->apn_provider); ++ ++ for (l = apn_methods; l; l = l->next, i++) ++ { ++ g_autoptr(CcWwanDataApn) apn = NULL; ++ ++ /* We don’t list MMS APNs */ ++ if (wwan_data_nma_method_is_mms (l->data)) ++ continue; ++ ++ apn = wwan_data_find_matching_apn (self, l->data); ++ ++ /* Prepend the item in order */ ++ if (!apn) ++ { ++ apn = cc_wwan_data_apn_new (); ++ g_list_store_insert (self->apn_list, i, apn); ++ } ++ ++ apn->access_method = l->data; ++ } ++} ++ ++static void ++wwan_data_update_apn_list (CcWwanData *self) ++{ ++ const GPtrArray *nm_connections; ++ guint i; ++ ++ if (self->apn_list || !self->sim) ++ return; ++ ++ if (!self->apn_list) ++ self->apn_list = g_list_store_new (CC_TYPE_WWAN_DATA_APN); ++ ++ if (self->nm_device) ++ { ++ nm_connections = nm_device_get_available_connections (self->nm_device); ++ ++ for (i = 0; i < nm_connections->len; i++) ++ { ++ g_autoptr(CcWwanDataApn) apn = NULL; ++ ++ apn = cc_wwan_data_apn_new (); ++ apn->remote_connection = g_object_ref (nm_connections->pdata[i]); ++ g_list_store_append (self->apn_list, apn); ++ ++ /* Load the default APN */ ++ if (!self->default_apn && self->sim_id) ++ { ++ NMSettingConnection *connection_setting; ++ NMSettingIPConfig *ip_setting; ++ NMSettingGsm *setting; ++ NMConnection *connection; ++ const gchar *sim_id; ++ ++ connection = NM_CONNECTION (apn->remote_connection); ++ setting = nm_connection_get_setting_gsm (connection); ++ connection_setting = nm_connection_get_setting_connection (connection); ++ sim_id = nm_setting_gsm_get_sim_id (setting); ++ ++ if (sim_id && *sim_id && g_str_equal (sim_id, self->sim_id)) ++ { ++ self->default_apn = apn; ++ self->home_only = nm_setting_gsm_get_home_only (setting); ++ self->data_enabled = nm_setting_connection_get_autoconnect (connection_setting); ++ ++ /* If any of the APN has a high priority, the device have high priority */ ++ ip_setting = nm_connection_get_setting_ip4_config (connection); ++ if (nm_setting_ip_config_get_route_metric (ip_setting) == CC_WWAN_ROUTE_PRIORITY_HIGH) ++ self->priority = CC_WWAN_APN_PRIORITY_HIGH; ++ } ++ } ++ } ++ } ++} ++ ++static void ++wwan_device_state_changed_cb (CcWwanData *self) ++{ ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLED]); ++} ++ ++static void ++cc_wwan_data_get_property (GObject *object, ++ guint prop_id, ++ GValue *value, ++ GParamSpec *pspec) ++{ ++ CcWwanData *self = (CcWwanData *)object; ++ ++ switch (prop_id) ++ { ++ case PROP_ERROR: ++ g_value_set_boolean (value, self->error != NULL); ++ break; ++ ++ case PROP_ENABLED: ++ g_value_set_boolean (value, cc_wwan_data_get_enabled (self)); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++cc_wwan_data_dispose (GObject *object) ++{ ++ CcWwanData *self = (CcWwanData *)object; ++ ++ g_clear_pointer (&self->sim_id, g_free); ++ g_clear_pointer (&self->operator_code, g_free); ++ g_clear_error (&self->error); ++ g_clear_object (&self->apn_list); ++ g_clear_object (&self->modem); ++ g_clear_object (&self->mm_object); ++ g_clear_object (&self->nm_client); ++ g_clear_object (&self->active_connection); ++ g_clear_object (&self->apn_db); ++ ++ G_OBJECT_CLASS (cc_wwan_data_parent_class)->dispose (object); ++} ++ ++static void ++cc_wwan_data_class_init (CcWwanDataClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->get_property = cc_wwan_data_get_property; ++ object_class->dispose = cc_wwan_data_dispose; ++ ++ properties[PROP_ERROR] = ++ g_param_spec_boolean ("error", ++ "Error", ++ "Set if some Error occurs", ++ FALSE, ++ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); ++ ++ properties[PROP_ENABLED] = ++ g_param_spec_boolean ("enabled", ++ "Enabled", ++ "Get if the data is enabled", ++ FALSE, ++ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++} ++ ++static void ++cc_wwan_data_init (CcWwanData *self) ++{ ++ self->home_only = TRUE; ++ self->priority = CC_WWAN_APN_PRIORITY_LOW; ++} ++ ++/** ++ * cc_wwan_data_new: ++ * @mm_object: An #MMObject ++ * @nm_client: An #NMClient ++ * ++ * Create a new device data representing the given ++ * @mm_object. If @mm_object isn’t a 3G/CDMA/LTE ++ * modem, %NULL will be returned ++ * ++ * Returns: A #CcWwanData or %NULL. ++ */ ++CcWwanData * ++cc_wwan_data_new (MMObject *mm_object, ++ NMClient *nm_client) ++{ ++ CcWwanData *self; ++ NMDevice *nm_device = NULL; ++ g_autoptr(MMModem) modem = NULL; ++ NMDeviceModemCapabilities capabilities = 0; ++ ++ g_return_val_if_fail (MM_IS_OBJECT (mm_object), NULL); ++ g_return_val_if_fail (NM_CLIENT (nm_client), NULL); ++ ++ modem = mm_object_get_modem (mm_object); ++ ++ if (modem) ++ nm_device = nm_client_get_device_by_iface (nm_client, ++ mm_modem_get_primary_port (modem)); ++ ++ if (NM_IS_DEVICE_MODEM (nm_device)) ++ capabilities = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (nm_device)); ++ ++ if (!(capabilities & (NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS ++ | NM_DEVICE_MODEM_CAPABILITY_LTE))) ++ return NULL; ++ ++ self = g_object_new (CC_TYPE_WWAN_DATA, NULL); ++ ++ self->nm_client = g_object_ref (nm_client); ++ self->mm_object = g_object_ref (mm_object); ++ self->modem = g_steal_pointer (&modem); ++ self->sim = mm_modem_get_sim_sync (self->modem, NULL, NULL); ++ self->sim_id = mm_sim_dup_identifier (self->sim); ++ self->operator_code = mm_sim_dup_operator_identifier (self->sim); ++ self->nm_device = g_object_ref (nm_device); ++ self->active_connection = nm_device_get_active_connection (nm_device); ++ ++ if (!self->operator_code) ++ { ++ MMModem3gpp *modem_3gpp; ++ ++ modem_3gpp = mm_object_peek_modem_3gpp (mm_object); ++ if (modem_3gpp) ++ self->operator_code = mm_modem_3gpp_dup_operator_code (modem_3gpp); ++ } ++ ++ if (self->active_connection) ++ g_object_ref (self->active_connection); ++ ++ g_signal_connect_object (self->nm_device, "notify::state", ++ G_CALLBACK (wwan_device_state_changed_cb), ++ self, G_CONNECT_SWAPPED); ++ ++ wwan_data_update_apn_list (self); ++ wwan_data_update_apn_list_db (self); ++ ++ return self; ++} ++ ++GError * ++cc_wwan_data_get_error (CcWwanData *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); ++ ++ return self->error; ++} ++ ++const gchar * ++cc_wwan_data_get_simple_html_error (CcWwanData *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); ++ ++ if (!self->error) ++ return NULL; ++ ++ if (g_error_matches (self->error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ return _("Operation Cancelled"); ++ ++ if (g_error_matches (self->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED)) ++ return _("Error: Access denied changing settings"); ++ ++ if (self->error->domain == MM_MOBILE_EQUIPMENT_ERROR) ++ return _("Error: Mobile Equipment Error"); ++ ++ return NULL; ++} ++ ++GListModel * ++cc_wwan_data_get_apn_list (CcWwanData *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); ++ ++ if (!self->apn_list) ++ wwan_data_update_apn_list (self); ++ ++ return G_LIST_MODEL (self->apn_list); ++} ++ ++static gboolean ++wwan_data_apn_is_new (CcWwanDataApn *apn) ++{ ++ return apn->remote_connection == NULL; ++} ++ ++static void ++wwan_data_update_apn (CcWwanData *self, ++ CcWwanDataApn *apn, ++ NMConnection *connection) ++{ ++ NMSetting *setting; ++ const gchar *name, *username, *password, *apn_name; ++ gint dns_priority, route_metric; ++ ++ setting = NM_SETTING (nm_connection_get_setting_connection (connection)); ++ ++ g_object_set (setting, ++ NM_SETTING_CONNECTION_AUTOCONNECT, self->data_enabled, ++ NULL); ++ ++ setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); ++ ++ g_object_set (setting, ++ NM_SETTING_GSM_HOME_ONLY, self->home_only, ++ NULL); ++ ++ setting = NM_SETTING (nm_connection_get_setting_ip4_config (connection)); ++ if (self->priority == CC_WWAN_APN_PRIORITY_HIGH && ++ self->default_apn == apn) ++ { ++ dns_priority = CC_WWAN_DNS_PRIORITY_HIGH; ++ route_metric = CC_WWAN_ROUTE_PRIORITY_HIGH; ++ } ++ else ++ { ++ dns_priority = CC_WWAN_DNS_PRIORITY_LOW; ++ route_metric = CC_WWAN_ROUTE_PRIORITY_LOW; ++ } ++ ++ g_object_set (setting, ++ NM_SETTING_IP_CONFIG_DNS_PRIORITY, dns_priority, ++ NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64)route_metric, ++ NULL); ++ ++ if (apn->access_method && !apn->remote_connection) ++ { ++ name = nma_mobile_access_method_get_name (apn->access_method); ++ username = nma_mobile_access_method_get_username (apn->access_method); ++ password = nma_mobile_access_method_get_password (apn->access_method); ++ apn_name = nma_mobile_access_method_get_3gpp_apn (apn->access_method); ++ } ++ else ++ { ++ return; ++ } ++ ++ setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); ++ g_object_set (setting, ++ NM_SETTING_GSM_USERNAME, username, ++ NM_SETTING_GSM_PASSWORD, password, ++ NM_SETTING_GSM_APN, apn_name, ++ NULL); ++ ++ setting = NM_SETTING (nm_connection_get_setting_connection (connection)); ++ ++ g_object_set (setting, ++ NM_SETTING_CONNECTION_ID, name, ++ NULL); ++} ++ ++static gint ++wwan_data_get_apn_index (CcWwanData *self, ++ CcWwanDataApn *apn) ++{ ++ GListModel *model; ++ guint i, n_items; ++ ++ model = G_LIST_MODEL (self->apn_list); ++ n_items = g_list_model_get_n_items (model); ++ ++ for (i = 0; i < n_items; i++) ++ { ++ g_autoptr(CcWwanDataApn) cached_apn = NULL; ++ ++ cached_apn = g_list_model_get_item (model, i); ++ ++ if (apn == cached_apn) ++ return i; ++ } ++ ++ return -1; ++} ++ ++static void ++cc_wwan_data_connection_updated_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanData *self; ++ CcWwanDataApn *apn; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ self = g_task_get_source_object (G_TASK (task)); ++ apn = g_task_get_task_data (G_TASK (task)); ++ ++ nm_remote_connection_commit_changes_finish (apn->remote_connection, ++ result, &error); ++ if (!error) ++ { ++ guint apn_index; ++ apn_index = wwan_data_get_apn_index (self, apn); ++ ++ if (apn_index >= 0) ++ g_list_model_items_changed (G_LIST_MODEL (self->apn_list), ++ apn_index, 1, 1); ++ else ++ g_warning ("APN ‘%s’ not in APN list", ++ cc_wwan_data_apn_get_name (apn)); ++ ++ apn->modified = FALSE; ++ g_task_return_boolean (task, TRUE); ++ } ++ else ++ { ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++} ++ ++static void ++cc_wwan_data_new_connection_added_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanData *self; ++ CcWwanDataApn *apn; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ self = g_task_get_source_object (G_TASK (task)); ++ apn = g_task_get_task_data (G_TASK (task)); ++ apn->remote_connection = nm_client_add_connection_finish (self->nm_client, ++ result, &error); ++ if (!error) ++ { ++ apn->modified = FALSE; ++ ++ /* If APN has access method, it’s already on the list */ ++ if (!apn->access_method) ++ { ++ g_list_store_append (self->apn_list, apn); ++ g_object_unref (apn); ++ } ++ ++ g_task_return_pointer (task, apn, NULL); ++ } ++ else ++ { ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++} ++ ++void ++cc_wwan_data_save_apn (CcWwanData *self, ++ CcWwanDataApn *apn, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ NMConnection *connection = NULL; ++ g_autoptr(GTask) task = NULL; ++ ++ g_return_if_fail (CC_IS_WWAN_DATA (self)); ++ g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); ++ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ ++ task = g_task_new (self, cancellable, callback, user_data); ++ g_task_set_task_data (task, apn, NULL); ++ ++ connection = wwan_data_get_nm_connection (apn); ++ ++ /* If the item has a remote connection, it should already be saved. ++ * We should save it again only if it got modified */ ++ if (apn->remote_connection && !apn->modified) ++ { ++ g_task_return_pointer (task, apn, NULL); ++ return; ++ } ++ ++ wwan_data_update_apn (self, apn, connection); ++ if (wwan_data_apn_is_new (apn)) ++ { ++ nm_client_add_connection_async (self->nm_client, apn->nm_connection, ++ TRUE, cancellable, ++ cc_wwan_data_new_connection_added_cb, ++ g_steal_pointer (&task)); ++ } ++ else ++ { ++ nm_remote_connection_commit_changes_async (apn->remote_connection, TRUE, ++ cancellable, ++ cc_wwan_data_connection_updated_cb, ++ g_steal_pointer (&task)); ++ } ++} ++ ++CcWwanDataApn * ++cc_wwan_data_save_apn_finish (CcWwanData *self, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); ++ g_return_val_if_fail (G_IS_TASK (result), NULL); ++ ++ return g_task_propagate_pointer (G_TASK (result), error); ++} ++ ++static void ++cc_wwan_data_activated_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanData *self; ++ NMActiveConnection *connection; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ self = g_task_get_source_object (G_TASK (task)); ++ connection = nm_client_activate_connection_finish (self->nm_client, ++ result, &error); ++ if (connection) ++ { ++ g_set_object (&self->active_connection, connection); ++ g_task_return_boolean (task, TRUE); ++ } ++ else ++ { ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ ++ if (error) ++ g_warning ("Error: %s", error->message); ++} ++ ++static void ++cc_wwan_data_settings_saved_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanData *self; ++ GCancellable *cancellable; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ self = g_task_get_source_object (G_TASK (task)); ++ cancellable = g_task_get_cancellable (G_TASK (task)); ++ ++ if (!cc_wwan_data_save_apn_finish (self, result, &error)) ++ { ++ g_task_return_error (task, g_steal_pointer (&error)); ++ return; ++ } ++ ++ self->default_apn->modified = FALSE; ++ ++ if (self->data_enabled) ++ { ++ nm_client_activate_connection_async (self->nm_client, ++ NM_CONNECTION (self->default_apn->remote_connection), ++ self->nm_device, ++ NULL, cancellable, ++ cc_wwan_data_activated_cb, ++ g_steal_pointer (&task)); ++ } ++ else ++ { ++ if (nm_device_disconnect (self->nm_device, cancellable, &error)) ++ { ++ g_clear_object (&self->active_connection); ++ g_task_return_boolean (task, TRUE); ++ } ++ else ++ { ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ } ++} ++ ++/** ++ * cc_wwan_data_save_settings: ++ * @cancellable: (nullable): a #GCancellable or %NULL ++ * @callback: a #GAsyncReadyCallback, or %NULL ++ * @user_data: closure data for @callback ++ * ++ * Save default settings to disk and apply changes. ++ * If the default APN has data enabled, the data is ++ * activated after the settings are saved. ++ * ++ * It’s a programmer error to call this function without ++ * a default APN set. ++ * Finish with cc_wwan_data_save_settings_finish(). ++ */ ++void ++cc_wwan_data_save_settings (CcWwanData *self, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ NMConnection *connection; ++ NMSetting *setting; ++ g_autoptr(GTask) task = NULL; ++ ++ g_return_if_fail (CC_IS_WWAN_DATA (self)); ++ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ g_return_if_fail (self->default_apn != NULL); ++ ++ task = g_task_new (self, cancellable, callback, user_data); ++ ++ /* Reset old settings to default value */ ++ if (self->old_default_apn && self->old_default_apn->remote_connection) ++ { ++ connection = NM_CONNECTION (self->old_default_apn->remote_connection); ++ ++ setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); ++ g_object_set (G_OBJECT (setting), ++ NM_SETTING_GSM_HOME_ONLY, TRUE, ++ NM_SETTING_GSM_SIM_ID, NULL, ++ NULL); ++ ++ setting = NM_SETTING (nm_connection_get_setting_ip4_config (connection)); ++ g_object_set (setting, ++ NM_SETTING_IP_CONFIG_DNS_PRIORITY, CC_WWAN_DNS_PRIORITY_LOW, ++ NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64)CC_WWAN_ROUTE_PRIORITY_LOW, ++ NULL); ++ ++ setting = NM_SETTING (nm_connection_get_setting_connection (connection)); ++ g_object_set (G_OBJECT (setting), ++ NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, ++ NULL); ++ ++ nm_remote_connection_commit_changes (NM_REMOTE_CONNECTION (connection), ++ TRUE, cancellable, NULL); ++ self->old_default_apn->modified = FALSE; ++ self->old_default_apn = NULL; ++ } ++ ++ self->default_apn->modified = TRUE; ++ connection = wwan_data_get_nm_connection (self->default_apn); ++ ++ setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); ++ g_object_set (G_OBJECT (setting), ++ NM_SETTING_GSM_HOME_ONLY, self->home_only, ++ NM_SETTING_GSM_SIM_ID, self->sim_id, ++ NULL); ++ ++ cc_wwan_data_save_apn (self, self->default_apn, cancellable, ++ cc_wwan_data_settings_saved_cb, ++ g_steal_pointer (&task)); ++} ++ ++gboolean ++cc_wwan_data_save_settings_finish (CcWwanData *self, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ ++gboolean ++cc_wwan_data_delete_apn (CcWwanData *self, ++ CcWwanDataApn *apn, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ NMRemoteConnection *connection = NULL; ++ gboolean ret = FALSE; ++ gint apn_index; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE); ++ g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE); ++ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), FALSE); ++ g_return_val_if_fail (error != NULL, FALSE); ++ ++ apn_index = wwan_data_get_apn_index (self, apn); ++ if (apn_index == -1) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, ++ "APN not found for the connection"); ++ return FALSE; ++ } ++ ++ connection = g_steal_pointer (&apn->remote_connection); ++ wwan_data_apn_reset (apn); ++ ++ if (connection) ++ ret = nm_remote_connection_delete (connection, cancellable, error); ++ ++ if (!ret) ++ { ++ apn->remote_connection = connection; ++ *error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Deleting APN from NetworkManager failed"); ++ return ret; ++ } ++ ++ g_object_unref (connection); ++ ++ /* We remove the item only if it's not in the mobile provider database */ ++ if (!apn->access_method) ++ { ++ if (self->default_apn == apn) ++ self->default_apn = NULL; ++ ++ g_list_store_remove (self->apn_list, apn_index); ++ ++ return TRUE; ++ } ++ ++ *error = g_error_new (G_IO_ERROR, G_IO_ERROR_READ_ONLY, ++ "Deleting APN from NetworkManager failed"); ++ return FALSE; ++} ++ ++CcWwanDataApn * ++cc_wwan_data_get_default_apn (CcWwanData *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); ++ ++ return self->default_apn; ++} ++ ++gboolean ++cc_wwan_data_set_default_apn (CcWwanData *self, ++ CcWwanDataApn *apn) ++{ ++ NMConnection *connection; ++ NMSetting *setting; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE); ++ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), FALSE); ++ g_return_val_if_fail (self->sim_id != NULL, FALSE); ++ ++ if (self->default_apn == apn) ++ return FALSE; ++ ++ /* ++ * APNs are bound to the SIM, not the modem device. ++ * This will let the APN work if the same SIM inserted ++ * in a different device, and not enable data if a ++ * different SIM is inserted to the modem. ++ */ ++ apn->modified = TRUE; ++ self->old_default_apn = self->default_apn; ++ self->default_apn = apn; ++ connection = wwan_data_get_nm_connection (apn); ++ setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); ++ g_object_set (G_OBJECT (setting), ++ NM_SETTING_GSM_SIM_ID, self->sim_id, NULL); ++ ++ return TRUE; ++} ++ ++gboolean ++cc_wwan_data_get_enabled (CcWwanData *self) ++{ ++ NMSettingConnection *setting; ++ NMConnection *connection; ++ NMDeviceState state; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE); ++ ++ state = nm_device_get_state (self->nm_device); ++ ++ if (state == NM_DEVICE_STATE_DISCONNECTED || ++ state == NM_DEVICE_STATE_DEACTIVATING) ++ if (nm_device_get_state_reason (self->nm_device) == NM_DEVICE_STATE_REASON_USER_REQUESTED) ++ return FALSE; ++ ++ if (nm_device_get_active_connection (self->nm_device) != NULL) ++ return TRUE; ++ ++ if (!self->default_apn || !self->default_apn->remote_connection) ++ return FALSE; ++ ++ connection = NM_CONNECTION (self->default_apn->remote_connection); ++ setting = nm_connection_get_setting_connection (connection); ++ ++ return nm_setting_connection_get_autoconnect (setting); ++} ++ ++/** ++ * cc_wwan_data_set_enabled: ++ * @self: A #CcWwanData ++ * @enable_data: whether to enable data ++ * ++ * Enable data for the device. The settings is ++ * saved to disk only after a default APN is set. ++ * ++ * If the data is enabled, the device will automatically ++ * turn data on everytime the same SIM is available. ++ * The data set is bound to the SIM, not the modem device. ++ * ++ * Use @cc_wwan_data_save_apn() with the default APN ++ * to save the changes and really enable/disable data. ++ */ ++void ++cc_wwan_data_set_enabled (CcWwanData *self, ++ gboolean enable_data) ++{ ++ g_return_if_fail (CC_IS_WWAN_DATA (self)); ++ ++ self->data_enabled = !!enable_data; ++ ++ if (self->default_apn) ++ self->default_apn->modified = TRUE; ++} ++ ++gboolean ++cc_wwan_data_get_roaming_enabled (CcWwanData *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE); ++ ++ if (!self->default_apn) ++ return FALSE; ++ ++ return !self->home_only; ++} ++ ++/** ++ * cc_wwan_data_apn_set_roaming_enabled: ++ * @self: A #CcWwanData ++ * @enable_roaming: whether to enable roaming or not ++ * ++ * Enable roaming for the device. The settings is ++ * saved to disk only after a default APN is set. ++ * ++ * Use @cc_wwan_data_save_apn() with the default APN ++ * to save the changes and really enable/disable data. ++ */ ++void ++cc_wwan_data_set_roaming_enabled (CcWwanData *self, ++ gboolean enable_roaming) ++{ ++ g_return_if_fail (CC_IS_WWAN_DATA (self)); ++ ++ self->home_only = !enable_roaming; ++ ++ if (self->default_apn) ++ self->default_apn->modified = TRUE; ++} ++ ++static void ++cc_wwan_data_apn_finalize (GObject *object) ++{ ++ CcWwanDataApn *apn = CC_WWAN_DATA_APN (object); ++ ++ wwan_data_apn_reset (apn); ++ g_clear_pointer (&apn->access_method, ++ nma_mobile_access_method_unref); ++ ++ G_OBJECT_CLASS (cc_wwan_data_parent_class)->finalize (object); ++} ++ ++static void ++cc_wwan_data_apn_class_init (CcWwanDataApnClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = cc_wwan_data_apn_finalize; ++} ++ ++static void ++cc_wwan_data_apn_init (CcWwanDataApn *apn) ++{ ++} ++ ++CcWwanDataApn * ++cc_wwan_data_apn_new (void) ++{ ++ return g_object_new (CC_TYPE_WWAN_DATA_APN, NULL); ++} ++ ++/** ++ * cc_wwan_data_apn_get_name: ++ * @apn: A #CcWwanDataApn ++ * ++ * Get the Name of @apn ++ * ++ * Returns: (transfer none): The Name of @apn ++ */ ++const gchar * ++cc_wwan_data_apn_get_name (CcWwanDataApn *apn) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), ""); ++ ++ if (apn->remote_connection) ++ return nm_connection_get_id (NM_CONNECTION (apn->remote_connection)); ++ ++ if (apn->access_method) ++ return nma_mobile_access_method_get_name (apn->access_method); ++ ++ return ""; ++} ++ ++/** ++ * cc_wwan_data_apn_set_name: ++ * @apn: A #CcWwanDataApn ++ * @name: The name to be given for APN, should not ++ * be empty ++ * ++ * Set the name of @apn to be @name. ++ * ++ * @apn is only modified, use @cc_wwan_data_save_apn() ++ * to save the changes. ++ */ ++void ++cc_wwan_data_apn_set_name (CcWwanDataApn *apn, ++ const gchar *name) ++{ ++ NMConnection *connection; ++ NMSettingConnection *setting; ++ ++ g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); ++ g_return_if_fail (name != NULL); ++ g_return_if_fail (*name != '\0'); ++ ++ if (g_str_equal (cc_wwan_data_apn_get_name (apn), name)) ++ return; ++ ++ apn->modified = TRUE; ++ connection = wwan_data_get_nm_connection (apn); ++ setting = nm_connection_get_setting_connection (connection); ++ g_object_set (G_OBJECT (setting), ++ NM_SETTING_CONNECTION_ID, name, ++ NULL); ++} ++ ++/** ++ * cc_wwan_data_apn_get_apn: ++ * @apn: A #CcWwanDataApn ++ * ++ * Get the APN of @apn ++ * ++ * Returns: (transfer none): The APN of @apn ++ */ ++const gchar * ++cc_wwan_data_apn_get_apn (CcWwanDataApn *apn) ++{ ++ const gchar *apn_name = ""; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), ""); ++ ++ if (apn->remote_connection) ++ { ++ NMSettingGsm *setting; ++ ++ setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection)); ++ apn_name = nm_setting_gsm_get_apn (setting); ++ } ++ else if (apn->access_method) ++ { ++ apn_name = nma_mobile_access_method_get_3gpp_apn (apn->access_method); ++ } ++ ++ return apn_name ? apn_name : ""; ++} ++ ++/** ++ * cc_wwan_data_apn_set_apn: ++ * @apn: A #CcWwanDataApn ++ * @apn_name: The apn to be used, should not be ++ * empty ++ * ++ * Set the APN of @apn to @apn_name. @apn_name is ++ * usually a URL like “example.com” or a simple string ++ * like “internet” ++ * ++ * @apn is only modified, use @cc_wwan_data_save_apn() ++ * to save the changes. ++ */ ++void ++cc_wwan_data_apn_set_apn (CcWwanDataApn *apn, ++ const gchar *apn_name) ++{ ++ NMConnection *connection; ++ NMSettingGsm *setting; ++ ++ g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); ++ g_return_if_fail (apn_name != NULL); ++ g_return_if_fail (*apn_name != '\0'); ++ ++ if (g_str_equal (cc_wwan_data_apn_get_apn (apn), apn_name)) ++ return; ++ ++ apn->modified = TRUE; ++ connection = wwan_data_get_nm_connection (apn); ++ setting = nm_connection_get_setting_gsm (connection); ++ g_object_set (G_OBJECT (setting), ++ NM_SETTING_GSM_APN, apn_name, ++ NULL); ++} ++ ++/** ++ * cc_wwan_data_apn_get_username: ++ * @apn: A #CcWwanDataApn ++ * ++ * Get the Username of @apn ++ * ++ * Returns: (transfer none): The Username of @apn ++ */ ++const gchar * ++cc_wwan_data_apn_get_username (CcWwanDataApn *apn) ++{ ++ const gchar *username = ""; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), ""); ++ ++ if (apn->remote_connection) ++ { ++ NMSettingGsm *setting; ++ ++ setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection)); ++ username = nm_setting_gsm_get_username (setting); ++ } ++ else if (apn->access_method) ++ { ++ username = nma_mobile_access_method_get_username (apn->access_method); ++ } ++ ++ return username ? username : ""; ++} ++ ++/** ++ * cc_wwan_data_apn_set_username: ++ * @apn: A #CcWwanDataAPN ++ * @username: The username to be used ++ * ++ * Set the Username of @apn to @username. ++ * ++ * @apn is only modified, use @cc_wwan_data_save_apn() ++ * to save the changes. ++ */ ++void ++cc_wwan_data_apn_set_username (CcWwanDataApn *apn, ++ const gchar *username) ++{ ++ NMConnection *connection; ++ NMSettingGsm *setting; ++ ++ g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); ++ ++ if (username && !*username) ++ username = NULL; ++ ++ if (g_strcmp0 (cc_wwan_data_apn_get_username (apn), username) == 0) ++ return; ++ ++ apn->modified = TRUE; ++ connection = wwan_data_get_nm_connection (apn); ++ setting = nm_connection_get_setting_gsm (connection); ++ g_object_set (G_OBJECT (setting), ++ NM_SETTING_GSM_USERNAME, username, ++ NULL); ++} ++ ++/** ++ * cc_wwan_data_apn_get_password: ++ * @apn: A #CcWwanDataApn ++ * ++ * Get the Password of @apn ++ * ++ * Returns: (transfer none): The Password of @apn ++ */ ++const gchar * ++cc_wwan_data_apn_get_password (CcWwanDataApn *apn) ++{ ++ const gchar *password = ""; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), ""); ++ ++ if (NM_IS_REMOTE_CONNECTION (apn->remote_connection)) ++ { ++ g_autoptr(GVariant) secrets = NULL; ++ g_autoptr(GError) error = NULL; ++ ++ secrets = nm_remote_connection_get_secrets (NM_REMOTE_CONNECTION (apn->remote_connection), ++ "gsm", NULL, &error); ++ ++ if (!error) ++ nm_connection_update_secrets (NM_CONNECTION (apn->remote_connection), ++ "gsm", secrets, &error); ++ ++ if (error) ++ { ++ g_warning ("Error: %s", error->message); ++ return ""; ++ } ++ } ++ ++ if (apn->remote_connection) ++ { ++ NMSettingGsm *setting; ++ ++ setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection)); ++ password = nm_setting_gsm_get_password (setting); ++ } ++ else if (apn->access_method) ++ { ++ password = nma_mobile_access_method_get_password (apn->access_method); ++ } ++ ++ return password ? password : ""; ++ ++ if (apn->remote_connection) ++ nm_connection_clear_secrets (NM_CONNECTION (apn->remote_connection)); ++} ++ ++/** ++ * cc_wwan_data_apn_set_password: ++ * @apn: A #CcWwanDataApn ++ * @password: The password to be used ++ * ++ * Set the Password of @apn to @password. ++ * ++ * @apn is only modified, use @cc_wwan_data_save_apn() ++ * to save the changes. ++ */ ++void ++cc_wwan_data_apn_set_password (CcWwanDataApn *apn, ++ const gchar *password) ++{ ++ NMConnection *connection; ++ NMSettingGsm *setting; ++ ++ g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); ++ ++ if (password && !*password) ++ password = NULL; ++ ++ if (g_strcmp0 (cc_wwan_data_apn_get_password (apn), password) == 0) ++ return; ++ ++ apn->modified = TRUE; ++ connection = wwan_data_get_nm_connection (apn); ++ setting = nm_connection_get_setting_gsm (connection); ++ g_object_set (G_OBJECT (setting), ++ NM_SETTING_GSM_PASSWORD, password, ++ NULL); ++} ++ ++gint ++cc_wwan_data_get_priority (CcWwanData *self) ++{ ++ CcWwanDataApn *apn; ++ NMSettingIPConfig *setting; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DATA (self), ++ CC_WWAN_APN_PRIORITY_LOW); ++ ++ apn = self->default_apn; ++ ++ if (!apn || !apn->remote_connection) ++ return CC_WWAN_APN_PRIORITY_LOW; ++ ++ setting = nm_connection_get_setting_ip4_config (NM_CONNECTION (apn->remote_connection)); ++ ++ /* Lower the number, higher the priority */ ++ if (nm_setting_ip_config_get_route_metric (setting) <= CC_WWAN_ROUTE_PRIORITY_HIGH) ++ return CC_WWAN_APN_PRIORITY_HIGH; ++ else ++ return CC_WWAN_APN_PRIORITY_LOW; ++} ++ ++void ++cc_wwan_data_set_priority (CcWwanData *self, ++ int priority) ++{ ++ g_return_if_fail (CC_IS_WWAN_DATA (self)); ++ g_return_if_fail (priority == CC_WWAN_APN_PRIORITY_LOW || ++ priority == CC_WWAN_APN_PRIORITY_HIGH); ++ ++ if (self->priority == priority) ++ return; ++ ++ self->priority = priority; ++ ++ if (self->default_apn) ++ self->default_apn->modified = TRUE; ++} +diff --git a/panels/wwan/cc-wwan-data.h b/panels/wwan/cc-wwan-data.h +new file mode 100644 +index 000000000..9572b862d +--- /dev/null ++++ b/panels/wwan/cc-wwan-data.h +@@ -0,0 +1,93 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-data.h ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++G_BEGIN_DECLS ++ ++#define CC_WWAN_APN_PRIORITY_LOW (1) ++#define CC_WWAN_APN_PRIORITY_HIGH (2) ++ ++#define CC_TYPE_WWAN_DATA_APN (cc_wwan_data_apn_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanDataApn, cc_wwan_data_apn, CC, WWAN_DATA_APN, GObject) ++ ++#define CC_TYPE_WWAN_DATA (cc_wwan_data_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanData, cc_wwan_data, CC, WWAN_DATA, GObject) ++ ++CcWwanData *cc_wwan_data_new (MMObject *mm_object, ++ NMClient *nm_client); ++GError *cc_wwan_data_get_error (CcWwanData *self); ++const gchar *cc_wwan_data_get_simple_html_error (CcWwanData *self); ++GListModel *cc_wwan_data_get_apn_list (CcWwanData *self); ++void cc_wwan_data_save_apn (CcWwanData *self, ++ CcWwanDataApn *apn, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++CcWwanDataApn *cc_wwan_data_save_apn_finish (CcWwanData *self, ++ GAsyncResult *result, ++ GError **error); ++void cc_wwan_data_save_settings (CcWwanData *self, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean cc_wwan_data_save_settings_finish (CcWwanData *self, ++ GAsyncResult *result, ++ GError **error); ++gboolean cc_wwan_data_delete_apn (CcWwanData *self, ++ CcWwanDataApn *apn, ++ GCancellable *cancellable, ++ GError **error); ++gboolean cc_wwan_data_set_default_apn (CcWwanData *self, ++ CcWwanDataApn *apn); ++CcWwanDataApn *cc_wwan_data_get_default_apn (CcWwanData *self); ++gboolean cc_wwan_data_get_enabled (CcWwanData *self); ++void cc_wwan_data_set_enabled (CcWwanData *self, ++ gboolean enabled); ++gboolean cc_wwan_data_get_roaming_enabled (CcWwanData *self); ++void cc_wwan_data_set_roaming_enabled (CcWwanData *self, ++ gboolean enable_roaming); ++ ++CcWwanDataApn *cc_wwan_data_apn_new (void); ++const gchar *cc_wwan_data_apn_get_name (CcWwanDataApn *apn); ++void cc_wwan_data_apn_set_name (CcWwanDataApn *apn, ++ const gchar *name); ++const gchar *cc_wwan_data_apn_get_apn (CcWwanDataApn *apn); ++void cc_wwan_data_apn_set_apn (CcWwanDataApn *apn, ++ const gchar *apn_name); ++const gchar *cc_wwan_data_apn_get_username (CcWwanDataApn *apn); ++void cc_wwan_data_apn_set_username (CcWwanDataApn *apn, ++ const gchar *username); ++const gchar *cc_wwan_data_apn_get_password (CcWwanDataApn *apn); ++void cc_wwan_data_apn_set_password (CcWwanDataApn *apn, ++ const gchar *password); ++gint cc_wwan_data_get_priority (CcWwanData *self); ++void cc_wwan_data_set_priority (CcWwanData *self, ++ int priority); ++ ++G_END_DECLS +diff --git a/panels/wwan/cc-wwan-details-dialog.c b/panels/wwan/cc-wwan-details-dialog.c +new file mode 100644 +index 000000000..59e8dc361 +--- /dev/null ++++ b/panels/wwan/cc-wwan-details-dialog.c +@@ -0,0 +1,257 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-network-dialog.c ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-details-dialog" ++ ++#include ++#include ++#include ++ ++#include "list-box-helper.h" ++#include "cc-wwan-details-dialog.h" ++#include "cc-wwan-resources.h" ++ ++/** ++ * @short_description: Dialog to Show Device Details ++ */ ++ ++struct _CcWwanDetailsDialog ++{ ++ GtkDialog parent_instance; ++ ++ GtkLabel *device_identifier; ++ GtkLabel *device_model; ++ GtkLabel *firmware_version; ++ GtkLabel *identifier_label; ++ GtkLabel *manufacturer; ++ GtkLabel *network_status; ++ GtkLabel *network_type; ++ GtkLabel *operator_name; ++ GtkLabel *own_numbers; ++ GtkLabel *signal_strength; ++ ++ CcWwanDevice *device; ++}; ++ ++G_DEFINE_TYPE (CcWwanDetailsDialog, cc_wwan_details_dialog, GTK_TYPE_DIALOG) ++ ++ ++enum { ++ PROP_0, ++ PROP_DEVICE, ++ N_PROPS ++}; ++ ++static GParamSpec *properties[N_PROPS]; ++ ++static void ++cc_wwan_details_update_network_status (CcWwanDetailsDialog *self) ++{ ++ CcWwanState state; ++ ++ g_assert (CC_IS_WWAN_DETAILS_DIALOG (self)); ++ ++ state = cc_wwan_device_get_network_state (self->device); ++ ++ switch (state) ++ { ++ case CC_WWAN_REGISTRATION_STATE_IDLE: ++ gtk_label_set_label (self->network_status, _("Not Registered")); ++ break; ++ ++ case CC_WWAN_REGISTRATION_STATE_REGISTERED: ++ gtk_label_set_label (self->network_status, _("Registered")); ++ break; ++ ++ case CC_WWAN_REGISTRATION_STATE_ROAMING: ++ gtk_label_set_label (self->network_status, _("Roaming")); ++ break; ++ ++ case CC_WWAN_REGISTRATION_STATE_SEARCHING: ++ gtk_label_set_label (self->network_status, _("Searching")); ++ break; ++ ++ case CC_WWAN_REGISTRATION_STATE_DENIED: ++ gtk_label_set_label (self->network_status, _("Denied")); ++ break; ++ ++ default: ++ gtk_label_set_label (self->network_status, _("Unknown")); ++ break; ++ } ++} ++ ++static void ++cc_wwan_details_signal_changed_cb (CcWwanDetailsDialog *self) ++{ ++ g_autofree gchar *network_type_string = NULL; ++ g_autofree gchar *signal_string = NULL; ++ const gchar *operator_name; ++ ++ g_assert (CC_IS_WWAN_DETAILS_DIALOG (self)); ++ ++ operator_name = cc_wwan_device_get_operator_name (self->device); ++ if (operator_name) ++ gtk_label_set_label (self->operator_name, operator_name); ++ ++ network_type_string = cc_wwan_device_dup_network_type_string (self->device); ++ if (network_type_string) ++ gtk_label_set_label (self->network_type, network_type_string); ++ ++ signal_string = cc_wwan_device_dup_signal_string (self->device); ++ if (signal_string) ++ gtk_label_set_label (self->signal_strength, signal_string); ++ ++ cc_wwan_details_update_network_status (self); ++} ++ ++static void ++cc_wwan_details_update_hardware_details (CcWwanDetailsDialog *self) ++{ ++ const gchar *str; ++ ++ g_assert (CC_IS_WWAN_DETAILS_DIALOG (self)); ++ ++ str = cc_wwan_device_get_manufacturer (self->device); ++ if (str) ++ gtk_label_set_label (self->manufacturer, str); ++ ++ str = cc_wwan_device_get_model (self->device); ++ if (str) ++ gtk_label_set_label (self->device_model, str); ++ ++ str = cc_wwan_device_get_firmware_version (self->device); ++ if (str) ++ gtk_label_set_label (self->firmware_version, str); ++ ++ str = cc_wwan_device_get_identifier (self->device); ++ if (str) ++ gtk_label_set_label (self->device_identifier, str); ++} ++ ++static void ++cc_wwan_details_dialog_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ CcWwanDetailsDialog *self = CC_WWAN_DETAILS_DIALOG (object); ++ ++ switch (prop_id) ++ { ++ case PROP_DEVICE: ++ self->device = g_value_dup_object (value); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++cc_wwan_details_dialog_constructed (GObject *object) ++{ ++ CcWwanDetailsDialog *self = CC_WWAN_DETAILS_DIALOG (object); ++ g_autofree char *numbers = NULL; ++ ++ G_OBJECT_CLASS (cc_wwan_details_dialog_parent_class)->constructed (object); ++ ++ g_signal_connect_object (self->device, "notify::signal", ++ G_CALLBACK (cc_wwan_details_signal_changed_cb), ++ self, G_CONNECT_SWAPPED); ++ ++ numbers = cc_wwan_device_dup_own_numbers (self->device); ++ gtk_widget_set_visible (GTK_WIDGET (self->own_numbers), !!numbers); ++ ++ if (numbers) ++ gtk_label_set_text (self->own_numbers, numbers); ++ ++ cc_wwan_details_signal_changed_cb (self); ++ cc_wwan_details_update_hardware_details (self); ++} ++ ++static void ++cc_wwan_details_dialog_dispose (GObject *object) ++{ ++ CcWwanDetailsDialog *self = CC_WWAN_DETAILS_DIALOG (object); ++ ++ g_clear_object (&self->device); ++ ++ G_OBJECT_CLASS (cc_wwan_details_dialog_parent_class)->dispose (object); ++} ++ ++static void ++cc_wwan_details_dialog_class_init (CcWwanDetailsDialogClass *klass) ++{ ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->set_property = cc_wwan_details_dialog_set_property; ++ object_class->constructed = cc_wwan_details_dialog_constructed; ++ object_class->dispose = cc_wwan_details_dialog_dispose; ++ ++ properties[PROP_DEVICE] = ++ g_param_spec_object ("device", ++ "Device", ++ "The WWAN Device", ++ CC_TYPE_WWAN_DEVICE, ++ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++ ++ gtk_widget_class_set_template_from_resource (widget_class, ++ "/org/gnome/control-center/wwan/cc-wwan-details-dialog.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, device_identifier); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, device_model); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, firmware_version); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, identifier_label); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, manufacturer); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, network_status); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, network_type); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, operator_name); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, own_numbers); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, signal_strength); ++} ++ ++static void ++cc_wwan_details_dialog_init (CcWwanDetailsDialog *self) ++{ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++} ++ ++CcWwanDetailsDialog * ++cc_wwan_details_dialog_new (GtkWindow *parent_window, ++ CcWwanDevice *device) ++{ ++ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL); ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL); ++ ++ return g_object_new (CC_TYPE_WWAN_DETAILS_DIALOG, ++ "transient-for", parent_window, ++ "use-header-bar", 1, ++ "device", device, ++ NULL); ++} +diff --git a/panels/wwan/cc-wwan-details-dialog.h b/panels/wwan/cc-wwan-details-dialog.h +new file mode 100644 +index 000000000..7e7812cde +--- /dev/null ++++ b/panels/wwan/cc-wwan-details-dialog.h +@@ -0,0 +1,40 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-details-dialog.h ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#include "cc-wwan-device.h" ++ ++G_BEGIN_DECLS ++ ++#define CC_TYPE_WWAN_DETAILS_DIALOG (cc_wwan_details_dialog_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanDetailsDialog, cc_wwan_details_dialog, CC, WWAN_DETAILS_DIALOG, GtkDialog) ++ ++CcWwanDetailsDialog *cc_wwan_details_dialog_new (GtkWindow *parent_window, ++ CcWwanDevice *device); ++ ++G_END_DECLS +diff --git a/panels/wwan/cc-wwan-details-dialog.ui b/panels/wwan/cc-wwan-details-dialog.ui +new file mode 100644 +index 000000000..042d3ee33 +--- /dev/null ++++ b/panels/wwan/cc-wwan-details-dialog.ui +@@ -0,0 +1,320 @@ ++ ++ ++ ++ +diff --git a/panels/wwan/cc-wwan-device-page.c b/panels/wwan/cc-wwan-device-page.c +new file mode 100644 +index 000000000..0a04d3379 +--- /dev/null ++++ b/panels/wwan/cc-wwan-device-page.c +@@ -0,0 +1,634 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-device-page.c ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-device-page" ++ ++#include ++#include ++#include ++#define GCR_API_SUBJECT_TO_CHANGE ++#include ++ ++#include "list-box-helper.h" ++#include "cc-list-row.h" ++#include "cc-wwan-data.h" ++#include "cc-wwan-mode-dialog.h" ++#include "cc-wwan-network-dialog.h" ++#include "cc-wwan-details-dialog.h" ++#include "cc-wwan-sim-lock-dialog.h" ++#include "cc-wwan-apn-dialog.h" ++#include "cc-wwan-device-page.h" ++#include "cc-wwan-resources.h" ++ ++#include "shell/cc-application.h" ++#include "shell/cc-debug.h" ++#include "shell/cc-object-storage.h" ++ ++/** ++ * @short_description: Device settings page ++ * @include: "cc-wwan-device-page.h" ++ * ++ * The Device page allows users to configure device ++ * settings. Please note that there is no one-to-one ++ * maping for a device settings page and a physical ++ * device. Say, if a device have two SIM card slots, ++ * there should be two device pages, one for each SIM. ++ */ ++ ++struct _CcWwanDevicePage ++{ ++ GtkBox parent_instance; ++ ++ GtkListBox *advanced_settings_list; ++ CcListRow *apn_settings_row; ++ CcListRow *data_enable_row; ++ CcListRow *data_roaming_row; ++ GtkListBox *data_settings_list; ++ CcListRow *details_row; ++ GtkStack *main_stack; ++ CcListRow *network_mode_row; ++ CcListRow *network_name_row; ++ GtkListBox *network_settings_list; ++ CcListRow *sim_lock_row; ++ GtkButton *unlock_button; ++ ++ GtkLabel *notification_label; ++ ++ CcWwanDevice *device; ++ CcWwanData *wwan_data; ++ GDBusProxy *wwan_proxy; ++ ++ CcWwanApnDialog *apn_dialog; ++ CcWwanDetailsDialog *details_dialog; ++ CcWwanModeDialog *network_mode_dialog; ++ CcWwanNetworkDialog *network_dialog; ++ CcWwanSimLockDialog *sim_lock_dialog; ++ ++ gint sim_index; ++ /* Set if a change is triggered in a signal’s callback, ++ * to avoid re-triggering of callback. This is used ++ * instead of blocking handlers where the signal may be ++ * emitted async and the block/unblock may not work right ++ */ ++ gboolean is_self_change; ++ gboolean is_retry; ++}; ++ ++G_DEFINE_TYPE (CcWwanDevicePage, cc_wwan_device_page, GTK_TYPE_BOX) ++ ++enum { ++ PROP_0, ++ PROP_DEVICE, ++ N_PROPS ++}; ++ ++static GParamSpec *properties[N_PROPS]; ++ ++static void ++wwan_data_show_apn_dialog (CcWwanDevicePage *self) ++{ ++ GtkWindow *top_level; ++ ++ top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))); ++ ++ if (!self->apn_dialog) ++ self->apn_dialog = cc_wwan_apn_dialog_new (top_level, self->device); ++ ++ gtk_widget_show (GTK_WIDGET (self->apn_dialog)); ++} ++ ++static GcrPrompt * ++cc_wwan_device_page_new_prompt (CcWwanDevicePage *self, ++ MMModemLock lock) ++{ ++ GcrPrompt *prompt; ++ g_autoptr(GError) error = NULL; ++ g_autofree gchar *description = NULL; ++ g_autofree gchar *warning = NULL; ++ const gchar *message = NULL; ++ guint num; ++ ++ prompt = GCR_PROMPT (gcr_system_prompt_open (-1, NULL, &error)); ++ ++ if (error) ++ { ++ g_warning ("Error opening Prompt: %s", error->message); ++ return NULL; ++ } ++ ++ gcr_prompt_set_title (prompt, _("Unlock SIM card")); ++ gcr_prompt_set_continue_label (prompt, _("Unlock")); ++ gcr_prompt_set_cancel_label (prompt, _("Cancel")); ++ ++ if (lock == MM_MODEM_LOCK_SIM_PIN) ++ { ++ description = g_strdup_printf (_("Please provide PIN code for SIM %d"), self->sim_index); ++ message = _("Enter PIN to unlock your SIM card"); ++ } ++ else if (lock == MM_MODEM_LOCK_SIM_PUK) ++ { ++ description = g_strdup_printf (_("Please provide PUK code for SIM %d"), self->sim_index); ++ message = _("Enter PUK to unlock your SIM card"); ++ } ++ else ++ { ++ g_warn_if_reached (); ++ g_object_unref (prompt); ++ ++ return NULL; ++ } ++ ++ gcr_prompt_set_description (prompt, description); ++ gcr_prompt_set_message (prompt, message); ++ ++ num = cc_wwan_device_get_unlock_retries (self->device, lock); ++ ++ if (num != MM_UNLOCK_RETRIES_UNKNOWN) ++ { ++ if (self->is_retry) ++ warning = g_strdup_printf (ngettext ("Wrong password entered. You have %1$u try left", ++ "Wrong password entered. You have %1$u tries left", num), num); ++ else ++ warning = g_strdup_printf (ngettext ("You have %u try left", ++ "You have %u tries left", num), num); ++ } ++ else if (self->is_retry) ++ { ++ warning = g_strdup (_("Wrong password entered.")); ++ } ++ ++ gcr_prompt_set_warning (prompt, warning); ++ ++ return prompt; ++} ++ ++static void ++wwan_update_unlock_button (CcWwanDevicePage *self) ++{ ++ gtk_button_set_label (self->unlock_button, _("Unlock")); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->unlock_button), TRUE); ++} ++ ++static void ++cc_wwan_device_page_unlocked_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanDevicePage *self = user_data; ++ wwan_update_unlock_button (self); ++} ++ ++static void ++wwan_device_unlock_clicked_cb (CcWwanDevicePage *self) ++{ ++ g_autoptr(GError) error = NULL; ++ GcrPrompt *prompt; ++ const gchar *password, *warning; ++ const gchar *pin = ""; ++ const gchar *puk = ""; ++ MMModemLock lock; ++ ++ lock = cc_wwan_device_get_lock (self->device); ++ password = ""; ++ ++ if (lock != MM_MODEM_LOCK_SIM_PIN && ++ lock != MM_MODEM_LOCK_SIM_PUK) ++ g_return_if_reached (); ++ ++ if (lock == MM_MODEM_LOCK_SIM_PUK) ++ { ++ prompt = cc_wwan_device_page_new_prompt (self, lock); ++ ++ warning = _("PUK code should be an 8 digit number"); ++ while (password && !cc_wwan_device_pin_valid (password, lock)) ++ { ++ password = gcr_prompt_password (prompt, NULL, &error); ++ gcr_prompt_set_warning (prompt, warning); ++ } ++ ++ puk = g_strdup (password); ++ password = ""; ++ gcr_prompt_close (prompt); ++ g_object_unref (prompt); ++ ++ if (error) ++ g_warning ("Error: %s", error->message); ++ ++ /* Error or User cancelled PUK */ ++ if (!puk) ++ return; ++ } ++ ++ prompt = cc_wwan_device_page_new_prompt (self, MM_MODEM_LOCK_SIM_PIN); ++ if (lock == MM_MODEM_LOCK_SIM_PUK) ++ { ++ gcr_prompt_set_password_new (prompt, TRUE); ++ gcr_prompt_set_message (prompt, _("Enter New PIN")); ++ gcr_prompt_set_warning (prompt, ""); ++ } ++ ++ warning = _("PIN code should be a 4-8 digit number"); ++ while (password && !cc_wwan_device_pin_valid (password, MM_MODEM_LOCK_SIM_PIN)) ++ { ++ password = gcr_prompt_password (prompt, NULL, &error); ++ gcr_prompt_set_warning (prompt, warning); ++ } ++ ++ pin = g_strdup (password); ++ gcr_prompt_close (prompt); ++ g_object_unref (prompt); ++ ++ if (error) ++ g_warning ("Error: %s", error->message); ++ ++ /* Error or User cancelled PIN */ ++ if (!pin) ++ return; ++ ++ gtk_button_set_label (self->unlock_button, _("Unlocking...")); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->unlock_button), FALSE); ++ ++ if (lock == MM_MODEM_LOCK_SIM_PIN) ++ cc_wwan_device_send_pin (self->device, pin, ++ NULL, /* cancellable */ ++ cc_wwan_device_page_unlocked_cb, ++ self); ++ else if (lock == MM_MODEM_LOCK_SIM_PUK) ++ { ++ cc_wwan_device_send_puk (self->device, puk, pin, ++ NULL, /* Cancellable */ ++ cc_wwan_device_page_unlocked_cb, ++ self); ++ } ++ else ++ { ++ g_warn_if_reached (); ++ } ++} ++ ++static void ++wwan_data_settings_changed_cb (CcWwanDevicePage *self, ++ GParamSpec *pspec, ++ CcListRow *data_row) ++{ ++ gboolean active; ++ ++ if (self->is_self_change) ++ { ++ self->is_self_change = FALSE; ++ return; ++ } ++ ++ if (cc_wwan_data_get_default_apn (self->wwan_data) == NULL) ++ wwan_data_show_apn_dialog (self); ++ ++ /* The user dismissed the dialog for selecting default APN */ ++ if (cc_wwan_data_get_default_apn (self->wwan_data) == NULL) ++ { ++ self->is_self_change = TRUE; ++ gtk_widget_activate (GTK_WIDGET (data_row)); ++ ++ return; ++ } ++ ++ active = cc_list_row_get_active (data_row); ++ ++ if (data_row == self->data_enable_row) ++ cc_wwan_data_set_enabled (self->wwan_data, active); ++ else ++ cc_wwan_data_set_roaming_enabled (self->wwan_data, active); ++ ++ cc_wwan_data_save_settings (self->wwan_data, NULL, NULL, NULL); ++} ++ ++static void ++wwan_network_settings_activated_cb (CcWwanDevicePage *self, ++ CcListRow *row) ++{ ++ GtkWidget *dialog; ++ GtkWindow *top_level; ++ ++ top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))); ++ ++ if (row == self->network_mode_row) ++ { ++ if (!self->network_mode_dialog) ++ self->network_mode_dialog = cc_wwan_mode_dialog_new (top_level, self->device); ++ ++ dialog = GTK_WIDGET (self->network_mode_dialog); ++ } ++ else if (row == self->network_name_row) ++ { ++ if (!self->network_dialog) ++ self->network_dialog = cc_wwan_network_dialog_new (top_level, self->device); ++ ++ dialog = GTK_WIDGET (self->network_dialog); ++ } ++ else ++ { ++ return; ++ } ++ ++ gtk_widget_show (dialog); ++} ++ ++static void ++wwan_advanced_settings_activated_cb (CcWwanDevicePage *self, ++ CcListRow *row) ++{ ++ GtkWindow *top_level; ++ ++ top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))); ++ ++ if (row == self->sim_lock_row) ++ { ++ if (!self->sim_lock_dialog) ++ self->sim_lock_dialog = cc_wwan_sim_lock_dialog_new (top_level, self->device); ++ gtk_widget_show (GTK_WIDGET (self->sim_lock_dialog)); ++ } ++ else if (row == self->details_row) ++ { ++ if (!self->details_dialog) ++ self->details_dialog = cc_wwan_details_dialog_new (top_level, self->device); ++ gtk_widget_show (GTK_WIDGET (self->details_dialog)); ++ } ++ else if (row == self->apn_settings_row) ++ { ++ wwan_data_show_apn_dialog (self); ++ } ++ else ++ { ++ g_return_if_reached (); ++ } ++} ++ ++static void ++cc_wwan_device_page_update_data (CcWwanDevicePage *self) ++{ ++ gboolean has_data; ++ ++ if (self->wwan_data == cc_wwan_device_get_data (self->device)) ++ return; ++ ++ self->wwan_data = cc_wwan_device_get_data (self->device); ++ has_data = self->wwan_data != NULL; ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->data_settings_list), has_data); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->apn_settings_row), has_data); ++ ++ if (!has_data) ++ return; ++ ++ g_signal_handlers_block_by_func (self->data_roaming_row, ++ wwan_data_settings_changed_cb, self); ++ g_signal_handlers_block_by_func (self->data_enable_row, ++ wwan_data_settings_changed_cb, self); ++ ++ g_object_set (self->data_roaming_row, "active", ++ cc_wwan_data_get_roaming_enabled (self->wwan_data), NULL); ++ ++ g_object_set (self->data_enable_row, "active", ++ cc_wwan_data_get_enabled (self->wwan_data), NULL); ++ ++ g_signal_handlers_unblock_by_func (self->data_roaming_row, ++ wwan_data_settings_changed_cb, self); ++ g_signal_handlers_unblock_by_func (self->data_enable_row, ++ wwan_data_settings_changed_cb, self); ++} ++ ++static void ++cc_wwan_device_page_update (CcWwanDevicePage *self) ++{ ++ GtkStack *main_stack; ++ MMModemLock lock; ++ ++ main_stack = self->main_stack; ++ if (!cc_wwan_device_has_sim (self->device)) ++ gtk_stack_set_visible_child_name (main_stack, "no-sim-view"); ++ else if ((lock = cc_wwan_device_get_lock (self->device)) == MM_MODEM_LOCK_SIM_PIN || ++ lock == MM_MODEM_LOCK_SIM_PUK) ++ gtk_stack_set_visible_child_name (main_stack, "sim-lock-view"); ++ else ++ gtk_stack_set_visible_child_name (main_stack, "settings-view"); ++} ++ ++static void ++cc_wwan_locks_changed_cb (CcWwanDevicePage *self) ++{ ++ const gchar *label; ++ ++ if (cc_wwan_device_get_sim_lock (self->device)) ++ label = _("Enabled"); ++ else ++ label = _("Disabled"); ++ ++ cc_list_row_set_secondary_label (self->sim_lock_row, label); ++ cc_wwan_device_page_update (self); ++} ++ ++static void ++cc_wwan_device_page_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ CcWwanDevicePage *self = (CcWwanDevicePage *)object; ++ ++ switch (prop_id) ++ { ++ case PROP_DEVICE: ++ self->device = g_value_dup_object (value); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++cc_wwan_device_page_constructed (GObject *object) ++{ ++ CcWwanDevicePage *self = (CcWwanDevicePage *)object; ++ ++ G_OBJECT_CLASS (cc_wwan_device_page_parent_class)->constructed (object); ++ ++ cc_wwan_device_page_update_data (self); ++ ++ g_object_bind_property (self->device, "operator-name", ++ self->network_name_row, "secondary-label", ++ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); ++ ++ g_object_bind_property (self->device, "network-mode", ++ self->network_mode_row, "secondary-label", ++ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); ++ ++ g_signal_connect_object (self->device, "notify::enabled-locks", ++ (GCallback)cc_wwan_locks_changed_cb, ++ self, G_CONNECT_SWAPPED); ++ ++ g_signal_connect_object (self->device, "notify::has-data", ++ (GCallback)cc_wwan_device_page_update_data, ++ self, G_CONNECT_SWAPPED); ++ ++ cc_wwan_device_page_update (self); ++ cc_wwan_locks_changed_cb (self); ++} ++ ++static void ++cc_wwan_device_page_dispose (GObject *object) ++{ ++ CcWwanDevicePage *self = (CcWwanDevicePage *)object; ++ ++ g_clear_pointer ((GtkWidget **)&self->apn_dialog, gtk_widget_destroy); ++ g_clear_pointer ((GtkWidget **)&self->details_dialog, gtk_widget_destroy); ++ g_clear_pointer ((GtkWidget **)&self->network_mode_dialog, gtk_widget_destroy); ++ g_clear_pointer ((GtkWidget **)&self->network_dialog, gtk_widget_destroy); ++ g_clear_pointer ((GtkWidget **)&self->sim_lock_dialog, gtk_widget_destroy); ++ ++ g_clear_object (&self->wwan_proxy); ++ g_clear_object (&self->device); ++ ++ G_OBJECT_CLASS (cc_wwan_device_page_parent_class)->dispose (object); ++} ++ ++static void ++cc_wwan_device_page_class_init (CcWwanDevicePageClass *klass) ++{ ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->set_property = cc_wwan_device_page_set_property; ++ object_class->constructed = cc_wwan_device_page_constructed; ++ object_class->dispose = cc_wwan_device_page_dispose; ++ ++ g_type_ensure (CC_TYPE_WWAN_DEVICE); ++ ++ properties[PROP_DEVICE] = ++ g_param_spec_object ("device", ++ "Device", ++ "The WWAN Device", ++ CC_TYPE_WWAN_DEVICE, ++ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++ ++ gtk_widget_class_set_template_from_resource (widget_class, ++ "/org/gnome/control-center/wwan/cc-wwan-device-page.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, advanced_settings_list); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, apn_settings_row); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, data_enable_row); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, data_roaming_row); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, data_settings_list); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, details_row); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, main_stack); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, network_mode_row); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, network_name_row); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, network_settings_list); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, sim_lock_row); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, unlock_button); ++ ++ gtk_widget_class_bind_template_callback (widget_class, wwan_device_unlock_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, wwan_data_settings_changed_cb); ++ gtk_widget_class_bind_template_callback (widget_class, wwan_network_settings_activated_cb); ++ gtk_widget_class_bind_template_callback (widget_class, wwan_advanced_settings_activated_cb); ++} ++ ++static void ++cc_wwan_device_page_init (CcWwanDevicePage *self) ++{ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++ ++ gtk_list_box_set_header_func (self->data_settings_list, ++ cc_list_box_update_header_func, ++ NULL, NULL); ++ ++ gtk_list_box_set_header_func (self->network_settings_list, ++ cc_list_box_update_header_func, ++ NULL, NULL); ++ ++ gtk_list_box_set_header_func (self->advanced_settings_list, ++ cc_list_box_update_header_func, ++ NULL, NULL); ++} ++ ++static void ++cc_wwan_error_changed_cb (CcWwanDevicePage *self) ++{ ++ const gchar *message; ++ ++ message = cc_wwan_device_get_simple_error (self->device); ++ ++ if (!message) ++ return; ++ ++ /* ++ * The label is first set to empty, which will result in ++ * the revealer to be closed. Then the real label is ++ * set. This will animate the revealer which can bring ++ * the user's attention. ++ */ ++ gtk_label_set_label (self->notification_label, ""); ++ gtk_label_set_label (self->notification_label, message); ++} ++ ++CcWwanDevicePage * ++cc_wwan_device_page_new (CcWwanDevice *device, ++ GtkWidget *notification_label) ++{ ++ CcWwanDevicePage *self; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL); ++ ++ self = g_object_new (CC_TYPE_WWAN_DEVICE_PAGE, ++ "device", device, ++ NULL); ++ ++ self->notification_label = GTK_LABEL (notification_label); ++ ++ g_signal_connect_object (self->device, "notify::error", ++ G_CALLBACK (cc_wwan_error_changed_cb), ++ self, G_CONNECT_SWAPPED); ++ ++ return self; ++} ++ ++CcWwanDevice * ++cc_wwan_device_page_get_device (CcWwanDevicePage *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE_PAGE (self), NULL); ++ ++ return self->device; ++} ++ ++void ++cc_wwan_device_page_set_sim_index (CcWwanDevicePage *self, ++ gint sim_index) ++{ ++ g_return_if_fail (CC_IS_WWAN_DEVICE_PAGE (self)); ++ g_return_if_fail (sim_index >= 1); ++ ++ self->sim_index = sim_index; ++} +diff --git a/panels/wwan/cc-wwan-device-page.h b/panels/wwan/cc-wwan-device-page.h +new file mode 100644 +index 000000000..923346a89 +--- /dev/null ++++ b/panels/wwan/cc-wwan-device-page.h +@@ -0,0 +1,42 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-device-page.h ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++ ++#include "cc-wwan-device.h" ++ ++G_BEGIN_DECLS ++ ++#define CC_TYPE_WWAN_DEVICE_PAGE (cc_wwan_device_page_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanDevicePage, cc_wwan_device_page, CC, WWAN_DEVICE_PAGE, GtkBox) ++ ++CcWwanDevicePage *cc_wwan_device_page_new (CcWwanDevice *device, ++ GtkWidget *notification_label); ++CcWwanDevice *cc_wwan_device_page_get_device (CcWwanDevicePage *self); ++void cc_wwan_device_page_set_sim_index (CcWwanDevicePage *self, ++ gint sim_index); ++ ++G_END_DECLS +diff --git a/panels/wwan/cc-wwan-device-page.ui b/panels/wwan/cc-wwan-device-page.ui +new file mode 100644 +index 000000000..f77bd707d +--- /dev/null ++++ b/panels/wwan/cc-wwan-device-page.ui +@@ -0,0 +1,270 @@ ++ ++ ++ ++ ++ both ++ ++ ++ ++ ++ ++ ++ +diff --git a/panels/wwan/cc-wwan-device.c b/panels/wwan/cc-wwan-device.c +new file mode 100644 +index 000000000..31baff95c +--- /dev/null ++++ b/panels/wwan/cc-wwan-device.c +@@ -0,0 +1,1355 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-device.c ++ * ++ * Copyright 2019-2020 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-device" ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include ++#include ++#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) ++# include ++# include ++#endif ++ ++#include "cc-wwan-errors-private.h" ++#include "cc-wwan-device.h" ++ ++/** ++ * @short_description: Device Object ++ * @include: "cc-wwan-device.h" ++ */ ++ ++struct _CcWwanDevice ++{ ++ GObject parent_instance; ++ ++ MMObject *mm_object; ++ MMModem *modem; ++ MMSim *sim; ++ MMModem3gpp *modem_3gpp; ++ ++ const char *operator_code; /* MCCMNC */ ++ GError *error; ++ ++ /* Building with NetworkManager is optional, ++ * so #NMclient type can’t be used here. ++ */ ++ GObject *nm_client; /* An #NMClient */ ++ CcWwanData *wwan_data; ++ ++ gulong modem_3gpp_id; ++ gulong modem_3gpp_locks_id; ++ ++ /* Enabled locks like PIN, PIN2, PUK, etc. */ ++ MMModem3gppFacility locks; ++ ++ CcWwanState registration_state; ++ gboolean network_is_manual; ++}; ++ ++G_DEFINE_TYPE (CcWwanDevice, cc_wwan_device, G_TYPE_OBJECT) ++ ++ ++enum { ++ PROP_0, ++ PROP_OPERATOR_NAME, ++ PROP_ENABLED_LOCKS, ++ PROP_ERROR, ++ PROP_HAS_DATA, ++ PROP_NETWORK_MODE, ++ PROP_REGISTRATION_STATE, ++ PROP_SIGNAL, ++ PROP_UNLOCK_REQUIRED, ++ N_PROPS ++}; ++ ++static GParamSpec *properties[N_PROPS]; ++ ++static void ++cc_wwan_device_state_changed_cb (CcWwanDevice *self) ++{ ++ MMModem3gppRegistrationState state; ++ ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_OPERATOR_NAME]); ++ ++ state = mm_modem_3gpp_get_registration_state (self->modem_3gpp); ++ ++ switch (state) ++ { ++ case MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN: ++ self->registration_state = CC_WWAN_REGISTRATION_STATE_UNKNOWN; ++ break; ++ ++ case MM_MODEM_3GPP_REGISTRATION_STATE_DENIED: ++ self->registration_state = CC_WWAN_REGISTRATION_STATE_DENIED; ++ break; ++ ++ case MM_MODEM_3GPP_REGISTRATION_STATE_IDLE: ++ self->registration_state = CC_WWAN_REGISTRATION_STATE_IDLE; ++ break; ++ ++ case MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING: ++ self->registration_state = CC_WWAN_REGISTRATION_STATE_SEARCHING; ++ break; ++ ++ case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING: ++ self->registration_state = CC_WWAN_REGISTRATION_STATE_ROAMING; ++ break; ++ ++ default: ++ self->registration_state = CC_WWAN_REGISTRATION_STATE_REGISTERED; ++ break; ++ } ++ ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REGISTRATION_STATE]); ++} ++ ++static void ++cc_wwan_device_locks_changed_cb (CcWwanDevice *self) ++{ ++ self->locks = mm_modem_3gpp_get_enabled_facility_locks (self->modem_3gpp); ++ ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLED_LOCKS]); ++} ++ ++static void ++cc_wwan_device_3gpp_changed_cb (CcWwanDevice *self) ++{ ++ gulong handler_id = 0; ++ ++ if (self->modem_3gpp_id) ++ g_signal_handler_disconnect (self->modem_3gpp, self->modem_3gpp_id); ++ self->modem_3gpp_id = 0; ++ ++ if (self->modem_3gpp_locks_id) ++ g_signal_handler_disconnect (self->modem_3gpp, self->modem_3gpp_locks_id); ++ self->modem_3gpp_locks_id = 0; ++ ++ g_clear_object (&self->modem_3gpp); ++ self->modem_3gpp = mm_object_get_modem_3gpp (self->mm_object); ++ ++ if (self->modem_3gpp) ++ { ++ handler_id = g_signal_connect_object (self->modem_3gpp, "notify::registration-state", ++ G_CALLBACK (cc_wwan_device_state_changed_cb), ++ self, G_CONNECT_SWAPPED); ++ self->modem_3gpp_id = handler_id; ++ ++ handler_id = g_signal_connect_object (self->modem_3gpp, "notify::enabled-facility-locks", ++ G_CALLBACK (cc_wwan_device_locks_changed_cb), ++ self, G_CONNECT_SWAPPED); ++ self->modem_3gpp_locks_id = handler_id; ++ cc_wwan_device_locks_changed_cb (self); ++ cc_wwan_device_state_changed_cb (self); ++ } ++} ++ ++static void ++cc_wwan_device_signal_quality_changed_cb (CcWwanDevice *self) ++{ ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SIGNAL]); ++} ++ ++static void ++cc_wwan_device_mode_changed_cb (CcWwanDevice *self) ++{ ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NETWORK_MODE]); ++} ++ ++static void ++wwan_device_emit_data_changed (CcWwanDevice *self) ++{ ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_DATA]); ++} ++ ++static void ++cc_wwan_device_unlock_required_cb (CcWwanDevice *self) ++{ ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UNLOCK_REQUIRED]); ++} ++ ++#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) ++static void ++cc_wwan_device_nm_changed_cb (CcWwanDevice *self, ++ GParamSpec *pspec, ++ NMClient *client) ++{ ++ gboolean nm_is_running; ++ ++ nm_is_running = nm_client_get_nm_running (client); ++ ++ if (!nm_is_running && self->wwan_data != NULL) ++ { ++ g_clear_object (&self->wwan_data); ++ wwan_device_emit_data_changed (self); ++ } ++} ++ ++static void ++cc_wwan_device_nm_device_added_cb (CcWwanDevice *self, ++ NMDevice *nm_device) ++{ ++ if (!NM_IS_DEVICE_MODEM (nm_device)) ++ return; ++ ++ if(!self->sim || !cc_wwan_device_is_nm_device (self, G_OBJECT (nm_device))) ++ return; ++ ++ self->wwan_data = cc_wwan_data_new (self->mm_object, ++ NM_CLIENT (self->nm_client)); ++ ++ if (self->wwan_data) ++ { ++ g_signal_connect_object (self->wwan_data, "notify::enabled", ++ G_CALLBACK (wwan_device_emit_data_changed), ++ self, G_CONNECT_SWAPPED); ++ wwan_device_emit_data_changed (self); ++ } ++} ++#endif ++ ++static void ++cc_wwan_device_get_property (GObject *object, ++ guint prop_id, ++ GValue *value, ++ GParamSpec *pspec) ++{ ++ CcWwanDevice *self = (CcWwanDevice *)object; ++ MMModemMode allowed, preferred; ++ ++ switch (prop_id) ++ { ++ case PROP_OPERATOR_NAME: ++ g_value_set_string (value, cc_wwan_device_get_operator_name (self)); ++ break; ++ ++ case PROP_ERROR: ++ g_value_set_boolean (value, self->error != NULL); ++ break; ++ ++ case PROP_HAS_DATA: ++ g_value_set_boolean (value, self->wwan_data != NULL); ++ break; ++ ++ case PROP_ENABLED_LOCKS: ++ g_value_set_int (value, self->locks); ++ break; ++ ++ case PROP_NETWORK_MODE: ++ if (cc_wwan_device_get_current_mode (self, &allowed, &preferred)) ++ g_value_take_string (value, cc_wwan_device_get_string_from_mode (self, allowed, preferred)); ++ break; ++ ++ case PROP_REGISTRATION_STATE: ++ g_value_set_int (value, self->registration_state); ++ break; ++ ++ case PROP_UNLOCK_REQUIRED: ++ g_value_set_int (value, cc_wwan_device_get_lock (self)); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++cc_wwan_device_dispose (GObject *object) ++{ ++ CcWwanDevice *self = (CcWwanDevice *)object; ++ ++ g_clear_error (&self->error); ++ g_clear_object (&self->modem); ++ g_clear_object (&self->mm_object); ++ g_clear_object (&self->sim); ++ g_clear_object (&self->modem_3gpp); ++ ++ g_clear_object (&self->nm_client); ++ g_clear_object (&self->wwan_data); ++ ++ G_OBJECT_CLASS (cc_wwan_device_parent_class)->dispose (object); ++} ++ ++static void ++cc_wwan_device_class_init (CcWwanDeviceClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->get_property = cc_wwan_device_get_property; ++ object_class->dispose = cc_wwan_device_dispose; ++ ++ properties[PROP_OPERATOR_NAME] = ++ g_param_spec_string ("operator-name", ++ "Operator Name", ++ "Operator Name the device is connected to", ++ NULL, ++ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); ++ ++ properties[PROP_ENABLED_LOCKS] = ++ g_param_spec_int ("enabled-locks", ++ "Enabled Locks", ++ "Locks Enabled in Modem", ++ MM_MODEM_3GPP_FACILITY_NONE, ++ MM_MODEM_3GPP_FACILITY_CORP_PERS, ++ MM_MODEM_3GPP_FACILITY_NONE, ++ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); ++ ++ properties[PROP_ERROR] = ++ g_param_spec_boolean ("error", ++ "Error", ++ "Set if some Error occurs", ++ FALSE, ++ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); ++ ++ properties[PROP_HAS_DATA] = ++ g_param_spec_boolean ("has-data", ++ "has-data", ++ "Data for the device", ++ FALSE, ++ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); ++ ++ properties[PROP_NETWORK_MODE] = ++ g_param_spec_string ("network-mode", ++ "Network Mode", ++ "A String representing preferred network mode", ++ NULL, ++ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); ++ ++ properties[PROP_REGISTRATION_STATE] = ++ g_param_spec_int ("registration-state", ++ "Registration State", ++ "The current network registration state", ++ CC_WWAN_REGISTRATION_STATE_UNKNOWN, ++ CC_WWAN_REGISTRATION_STATE_DENIED, ++ CC_WWAN_REGISTRATION_STATE_UNKNOWN, ++ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); ++ ++ properties[PROP_UNLOCK_REQUIRED] = ++ g_param_spec_int ("unlock-required", ++ "Unlock Required", ++ "The Modem lock status changed", ++ MM_MODEM_LOCK_UNKNOWN, ++ MM_MODEM_LOCK_PH_NETSUB_PUK, ++ MM_MODEM_LOCK_UNKNOWN, ++ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); ++ ++ properties[PROP_SIGNAL] = ++ g_param_spec_int ("signal", ++ "Signal", ++ "Get Device Signal", ++ 0, 100, 0, ++ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++} ++ ++static void ++cc_wwan_device_init (CcWwanDevice *self) ++{ ++} ++ ++/** ++ * cc_wwan_device_new: ++ * @mm_object: (transfer full): An #MMObject ++ * ++ * Create a new device representing the given ++ * @mm_object. ++ * ++ * Returns: A #CcWwanDevice ++ */ ++CcWwanDevice * ++cc_wwan_device_new (MMObject *mm_object, ++ GObject *nm_client) ++{ ++ CcWwanDevice *self; ++ ++ g_return_val_if_fail (MM_IS_OBJECT (mm_object), NULL); ++#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) ++ g_return_val_if_fail (NM_IS_CLIENT (nm_client), NULL); ++#else ++ g_return_val_if_fail (!nm_client, NULL); ++#endif ++ ++ self = g_object_new (CC_TYPE_WWAN_DEVICE, NULL); ++ ++ self->mm_object = g_object_ref (mm_object); ++ self->modem = mm_object_get_modem (mm_object); ++ self->sim = mm_modem_get_sim_sync (self->modem, NULL, NULL); ++ g_set_object (&self->nm_client, nm_client); ++ if (self->sim) ++ { ++ self->operator_code = mm_sim_get_operator_identifier (self->sim); ++#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) ++ self->wwan_data = cc_wwan_data_new (mm_object, ++ NM_CLIENT (self->nm_client)); ++#endif ++ } ++ ++ g_signal_connect_object (self->mm_object, "notify::unlock-required", ++ G_CALLBACK (cc_wwan_device_unlock_required_cb), ++ self, G_CONNECT_SWAPPED); ++ if (self->wwan_data) ++ g_signal_connect_object (self->wwan_data, "notify::enabled", ++ G_CALLBACK (wwan_device_emit_data_changed), ++ self, G_CONNECT_SWAPPED); ++ ++#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) ++ g_signal_connect_object (self->nm_client, "notify::nm-running" , ++ G_CALLBACK (cc_wwan_device_nm_changed_cb), self, ++ G_CONNECT_SWAPPED); ++ ++ g_signal_connect_object (self->nm_client, "device-added", ++ G_CALLBACK (cc_wwan_device_nm_device_added_cb), ++ self, G_CONNECT_SWAPPED); ++#endif ++ ++ g_signal_connect_object (self->mm_object, "notify::modem3gpp", ++ G_CALLBACK (cc_wwan_device_3gpp_changed_cb), ++ self, G_CONNECT_SWAPPED); ++ g_signal_connect_object (self->modem, "notify::signal-quality", ++ G_CALLBACK (cc_wwan_device_signal_quality_changed_cb), ++ self, G_CONNECT_SWAPPED); ++ ++ cc_wwan_device_3gpp_changed_cb (self); ++ g_signal_connect_object (self->modem, "notify::current-modes", ++ G_CALLBACK (cc_wwan_device_mode_changed_cb), ++ self, G_CONNECT_SWAPPED); ++ ++ return self; ++} ++ ++gboolean ++cc_wwan_device_has_sim (CcWwanDevice *self) ++{ ++ MMModemStateFailedReason state_reason; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ ++ state_reason = mm_modem_get_state_failed_reason (self->modem); ++ ++ if (state_reason == MM_MODEM_STATE_FAILED_REASON_SIM_MISSING) ++ return FALSE; ++ ++ return TRUE; ++} ++ ++/** ++ * cc_wwan_device_get_lock: ++ * @self: a #CcWwanDevice ++ * ++ * Get the active device lock that is required to ++ * be unlocked for accessing device features. ++ * ++ * Returns: %TRUE if PIN enabled, %FALSE otherwise. ++ */ ++MMModemLock ++cc_wwan_device_get_lock (CcWwanDevice *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), MM_MODEM_LOCK_UNKNOWN); ++ ++ return mm_modem_get_unlock_required (self->modem); ++} ++ ++ ++/** ++ * cc_wwan_device_get_sim_lock: ++ * @self: a #CcWwanDevice ++ * ++ * Get if SIM lock with PIN is enabled. SIM PIN ++ * enabled doesn’t mean that SIM is locked. ++ * See cc_wwan_device_get_lock(). ++ * ++ * Returns: %TRUE if PIN enabled, %FALSE otherwise. ++ */ ++gboolean ++cc_wwan_device_get_sim_lock (CcWwanDevice *self) ++{ ++ gboolean sim_lock; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ ++ sim_lock = self->locks & MM_MODEM_3GPP_FACILITY_SIM; ++ ++ return !!sim_lock; ++} ++ ++guint ++cc_wwan_device_get_unlock_retries (CcWwanDevice *self, ++ MMModemLock lock) ++{ ++ MMUnlockRetries *retries; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), 0); ++ ++ retries = mm_modem_get_unlock_retries (self->modem); ++ ++ return mm_unlock_retries_get (retries, lock); ++} ++ ++static void ++cc_wwan_device_pin_sent_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanDevice *self; ++ MMSim *sim = (MMSim *)object; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ if (!mm_sim_send_pin_finish (sim, result, &error)) ++ { ++ self = g_task_get_source_object (G_TASK (task)); ++ ++ g_clear_error (&self->error); ++ self->error = g_error_copy (error); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); ++ ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ else ++ { ++ g_task_return_boolean (task, TRUE); ++ } ++} ++ ++void ++cc_wwan_device_send_pin (CcWwanDevice *self, ++ const gchar *pin, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr(GTask) task = NULL; ++ ++ g_return_if_fail (CC_IS_WWAN_DEVICE (self)); ++ g_return_if_fail (MM_IS_SIM (self->sim)); ++ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ g_return_if_fail (pin && *pin); ++ ++ task = g_task_new (self, cancellable, callback, user_data); ++ ++ mm_sim_send_pin (self->sim, pin, cancellable, ++ cc_wwan_device_pin_sent_cb, ++ g_steal_pointer (&task)); ++} ++ ++gboolean ++cc_wwan_device_send_pin_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ ++static void ++cc_wwan_device_puk_sent_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanDevice *self; ++ MMSim *sim = (MMSim *)object; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ if (!mm_sim_send_puk_finish (sim, result, &error)) ++ { ++ self = g_task_get_source_object (G_TASK (task)); ++ ++ g_clear_error (&self->error); ++ self->error = g_error_copy (error); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); ++ ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ else ++ { ++ g_task_return_boolean (task, TRUE); ++ } ++} ++ ++void ++cc_wwan_device_send_puk (CcWwanDevice *self, ++ const gchar *puk, ++ const gchar *pin, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr(GTask) task = NULL; ++ ++ g_return_if_fail (CC_IS_WWAN_DEVICE (self)); ++ g_return_if_fail (MM_IS_SIM (self->sim)); ++ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ g_return_if_fail (puk && *puk); ++ g_return_if_fail (pin && *pin); ++ ++ task = g_task_new (self, cancellable, callback, user_data); ++ ++ mm_sim_send_puk (self->sim, puk, pin, cancellable, ++ cc_wwan_device_puk_sent_cb, ++ g_steal_pointer (&task)); ++} ++ ++gboolean ++cc_wwan_device_send_puk_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ ++static void ++cc_wwan_device_enable_pin_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanDevice *self; ++ MMSim *sim = (MMSim *)object; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ if (!mm_sim_enable_pin_finish (sim, result, &error)) ++ { ++ self = g_task_get_source_object (G_TASK (task)); ++ ++ g_clear_error (&self->error); ++ self->error = g_error_copy (error); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); ++ ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ else ++ { ++ g_task_return_boolean (task, TRUE); ++ } ++} ++ ++void ++cc_wwan_device_enable_pin (CcWwanDevice *self, ++ const gchar *pin, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr(GTask) task = NULL; ++ ++ g_return_if_fail (CC_IS_WWAN_DEVICE (self)); ++ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ g_return_if_fail (pin && *pin); ++ ++ task = g_task_new (self, cancellable, callback, user_data); ++ ++ mm_sim_enable_pin (self->sim, pin, cancellable, ++ cc_wwan_device_enable_pin_cb, ++ g_steal_pointer (&task)); ++} ++ ++gboolean ++cc_wwan_device_enable_pin_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ ++static void ++cc_wwan_device_disable_pin_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanDevice *self; ++ MMSim *sim = (MMSim *)object; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ if (!mm_sim_disable_pin_finish (sim, result, &error)) ++ { ++ self = g_task_get_source_object (G_TASK (task)); ++ ++ g_clear_error (&self->error); ++ self->error = g_error_copy (error); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); ++ ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ else ++ { ++ g_task_return_boolean (task, TRUE); ++ } ++} ++ ++void ++cc_wwan_device_disable_pin (CcWwanDevice *self, ++ const gchar *pin, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr(GTask) task = NULL; ++ ++ g_return_if_fail (CC_IS_WWAN_DEVICE (self)); ++ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ g_return_if_fail (pin && *pin); ++ ++ task = g_task_new (self, cancellable, callback, user_data); ++ ++ mm_sim_disable_pin (self->sim, pin, cancellable, ++ cc_wwan_device_disable_pin_cb, ++ g_steal_pointer (&task)); ++} ++ ++gboolean ++cc_wwan_device_disable_pin_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ ++static void ++cc_wwan_device_change_pin_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanDevice *self; ++ MMSim *sim = (MMSim *)object; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ if (!mm_sim_change_pin_finish (sim, result, &error)) ++ { ++ self = g_task_get_source_object (G_TASK (task)); ++ ++ g_clear_error (&self->error); ++ self->error = g_error_copy (error); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); ++ ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ else ++ { ++ g_task_return_boolean (task, TRUE); ++ } ++} ++ ++void ++cc_wwan_device_change_pin (CcWwanDevice *self, ++ const gchar *old_pin, ++ const gchar *new_pin, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr(GTask) task = NULL; ++ ++ g_return_if_fail (CC_IS_WWAN_DEVICE (self)); ++ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ g_return_if_fail (old_pin && *old_pin); ++ g_return_if_fail (new_pin && *new_pin); ++ ++ task = g_task_new (self, cancellable, callback, user_data); ++ ++ mm_sim_change_pin (self->sim, old_pin, new_pin, cancellable, ++ cc_wwan_device_change_pin_cb, ++ g_steal_pointer (&task)); ++} ++ ++gboolean ++cc_wwan_device_change_pin_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ ++static void ++cc_wwan_device_network_mode_set_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanDevice *self; ++ MMModem *modem = (MMModem *)object; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ if (!mm_modem_set_current_modes_finish (modem, result, &error)) ++ { ++ self = g_task_get_source_object (G_TASK (task)); ++ ++ g_clear_error (&self->error); ++ self->error = g_error_copy (error); ++ g_warning ("Error: %s", error->message); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); ++ ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ else ++ { ++ g_task_return_boolean (task, TRUE); ++ } ++} ++ ++/** ++ * cc_wwan_device_set_network_mode: ++ * @self: a #CcWwanDevice ++ * @allowed: The allowed #MMModemModes ++ * @preferred: The preferred #MMModemMode ++ * @cancellable: (nullable): a #GCancellable or %NULL ++ * @callback: (nullable): a #GAsyncReadyCallback or %NULL ++ * @user_data: (nullable): closure data for @callback ++ * ++ * Asynchronously set preferred network mode. ++ * ++ * Call @cc_wwan_device_set_current_mode_finish() ++ * in @callback to get the result of operation. ++ */ ++void ++cc_wwan_device_set_current_mode (CcWwanDevice *self, ++ MMModemMode allowed, ++ MMModemMode preferred, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr(GTask) task = NULL; ++ GPermission *permission; ++ g_autoptr(GError) error = NULL; ++ ++ g_return_if_fail (CC_IS_WWAN_DEVICE (self)); ++ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ ++ task = g_task_new (self, cancellable, callback, user_data); ++ permission = polkit_permission_new_sync ("org.freedesktop.ModemManager1.Device.Control", ++ NULL, cancellable, &error); ++ if (permission) ++ g_task_set_task_data (task, permission, g_object_unref); ++ ++ if (error) ++ g_warning ("error: %s", error->message); ++ ++ if (error) ++ { ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ else if (!g_permission_get_allowed (permission)) ++ { ++ error = g_error_new (G_IO_ERROR, ++ G_IO_ERROR_PERMISSION_DENIED, ++ "Access Denied"); ++ g_clear_error (&self->error); ++ self->error = g_error_copy (error); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); ++ ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ else ++ { ++ mm_modem_set_current_modes (self->modem, allowed, preferred, ++ cancellable, cc_wwan_device_network_mode_set_cb, ++ g_steal_pointer (&task)); ++ } ++} ++ ++/** ++ * cc_wwan_device_set_current_mode_finish: ++ * @self: a #CcWwanDevice ++ * @result: a #GAsyncResult ++ * @error: a location for #GError or %NULL ++ * ++ * Get the status whether setting network mode ++ * succeeded ++ * ++ * Returns: %TRUE if network mode was successfully set, ++ * %FALSE otherwise. ++ */ ++gboolean ++cc_wwan_device_set_current_mode_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ ++gboolean ++cc_wwan_device_get_current_mode (CcWwanDevice *self, ++ MMModemMode *allowed, ++ MMModemMode *preferred) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ ++ return mm_modem_get_current_modes (self->modem, allowed, preferred); ++} ++ ++gboolean ++cc_wwan_device_is_auto_network (CcWwanDevice *self) ++{ ++ /* ++ * XXX: ModemManager Doesn’t have a true API to check ++ * if registration is automatic or manual. So Let’s ++ * do some guess work. ++ */ ++ if (self->registration_state == CC_WWAN_REGISTRATION_STATE_DENIED) ++ return FALSE; ++ ++ return !self->network_is_manual; ++} ++ ++CcWwanState ++cc_wwan_device_get_network_state (CcWwanDevice *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), 0); ++ ++ return self->registration_state; ++} ++ ++gboolean ++cc_wwan_device_get_supported_modes (CcWwanDevice *self, ++ MMModemMode *allowed, ++ MMModemMode *preferred) ++{ ++ g_autofree MMModemModeCombination *modes = NULL; ++ guint n_modes, i; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ ++ if (!mm_modem_get_supported_modes (self->modem, &modes, &n_modes)) ++ return FALSE; ++ ++ if (allowed) ++ *allowed = 0; ++ if (preferred) ++ *preferred = 0; ++ ++ for (i = 0; i < n_modes; i++) ++ { ++ if (allowed) ++ *allowed = *allowed | modes[i].allowed; ++ if (preferred) ++ *preferred = *preferred | modes[i].preferred; ++ } ++ ++ return TRUE; ++} ++ ++#define APPEND_MODE_TO_STRING(_str, _now, _preferred, _mode_str) do { \ ++ if (_str->len > 0) \ ++ g_string_append (_str, ", "); \ ++ g_string_append (_str, _mode_str); \ ++ if (_preferred == _now) \ ++ g_string_append (_str, _(" (Preferred)")); \ ++ } while (0) ++ ++gchar * ++cc_wwan_device_get_string_from_mode (CcWwanDevice *self, ++ MMModemMode allowed, ++ MMModemMode preferred) ++{ ++ GString *str; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ g_return_val_if_fail (allowed != 0, NULL); ++ ++ str = g_string_sized_new (10); ++ ++ if (allowed & MM_MODEM_MODE_2G) ++ APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_2G, preferred, "2G"); ++ if (allowed & MM_MODEM_MODE_3G) ++ APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_3G, preferred, "3G"); ++ if (allowed & MM_MODEM_MODE_4G) ++ APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_4G, preferred, "4G"); ++ ++ if (allowed == MM_MODEM_MODE_2G || ++ allowed == MM_MODEM_MODE_3G || ++ allowed == MM_MODEM_MODE_4G) ++ g_string_append (str, _(" Only")); ++ ++ if (str->len == 0) ++ return g_string_free (str, TRUE); ++ else ++ return g_string_free (str, FALSE); ++} ++#undef APPEND_MODE_TO_STRING ++ ++static void ++wwan_network_list_free (GList *network_list) ++{ ++ g_list_free_full (network_list, (GDestroyNotify)mm_modem_3gpp_network_free); ++} ++ ++static void ++cc_wwan_device_scan_complete_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ MMModem3gpp *modem_3gpp = (MMModem3gpp *)object; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ GList *network_list; ++ ++ network_list = mm_modem_3gpp_scan_finish (modem_3gpp, result, &error); ++ ++ if (error) ++ g_task_return_error (task, g_steal_pointer (&error)); ++ else ++ g_task_return_pointer (task, network_list, (GDestroyNotify)wwan_network_list_free); ++} ++ ++void ++cc_wwan_device_scan_networks (CcWwanDevice *self, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr(GTask) task = NULL; ++ ++ g_return_if_fail (CC_IS_WWAN_DEVICE (self)); ++ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ ++ task = g_task_new (self, cancellable, callback, user_data); ++ ++ mm_modem_3gpp_scan (self->modem_3gpp, cancellable, ++ cc_wwan_device_scan_complete_cb, ++ g_steal_pointer (&task)); ++} ++ ++GList * ++cc_wwan_device_scan_networks_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ ++ return g_task_propagate_pointer (G_TASK (result), error); ++} ++ ++static void ++cc_wwan_device_register_network_complete_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ CcWwanDevice *self; ++ MMModem3gpp *modem_3gpp = (MMModem3gpp *)object; ++ g_autoptr(GTask) task = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ if (!mm_modem_3gpp_register_finish (modem_3gpp, result, &error)) ++ { ++ self = g_task_get_source_object (G_TASK (task)); ++ ++ g_clear_error (&self->error); ++ self->error = g_error_copy (error); ++ g_warning ("Error: %s", error->message); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); ++ ++ g_task_return_error (task, g_steal_pointer (&error)); ++ } ++ else ++ { ++ g_task_return_boolean (task, TRUE); ++ } ++} ++ ++void ++cc_wwan_device_register_network (CcWwanDevice *self, ++ const gchar *network_id, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr(GTask) task = NULL; ++ ++ g_return_if_fail (CC_IS_WWAN_DEVICE (self)); ++ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ ++ task = g_task_new (self, cancellable, callback, user_data); ++ ++ if (network_id && *network_id) ++ self->network_is_manual = TRUE; ++ else ++ self->network_is_manual = FALSE; ++ ++ mm_modem_3gpp_register (self->modem_3gpp, network_id, cancellable, ++ cc_wwan_device_register_network_complete_cb, ++ g_steal_pointer (&task)); ++} ++ ++gboolean ++cc_wwan_device_register_network_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ ++/** ++ * cc_wwan_device_get_operator_name: ++ * @self: a #CcWwanDevice ++ * ++ * Get the human readable network operator name ++ * currently the device is connected to. ++ * ++ * Returns: (nullable): The operator name or %NULL ++ */ ++const gchar * ++cc_wwan_device_get_operator_name (CcWwanDevice *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ ++ if (!self->modem_3gpp) ++ return NULL; ++ ++ return mm_modem_3gpp_get_operator_name (self->modem_3gpp); ++} ++ ++gchar * ++cc_wwan_device_dup_own_numbers (CcWwanDevice *self) ++{ ++ const char *const *own_numbers; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ ++ own_numbers = mm_modem_get_own_numbers (self->modem); ++ ++ if (!own_numbers) ++ return NULL; ++ ++ return g_strjoinv ("\n", (char **)own_numbers); ++} ++ ++gchar * ++cc_wwan_device_dup_network_type_string (CcWwanDevice *self) ++{ ++ MMModemAccessTechnology type; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ ++ type = mm_modem_get_access_technologies (self->modem); ++ ++ return mm_modem_access_technology_build_string_from_mask (type); ++} ++ ++gchar * ++cc_wwan_device_dup_signal_string (CcWwanDevice *self) ++{ ++ MMModemSignal *modem_signal; ++ MMSignal *signal; ++ GString *str; ++ gdouble value; ++ gboolean recent; ++ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ ++ modem_signal = mm_object_peek_modem_signal (self->mm_object); ++ ++ if (!modem_signal) ++ return g_strdup_printf ("%d%%", mm_modem_get_signal_quality (self->modem, &recent)); ++ ++ str = g_string_new (""); ++ ++ /* Adapted from ModemManager mmcli-modem-signal.c */ ++ signal = mm_modem_signal_peek_cdma (modem_signal); ++ if (signal) ++ { ++ if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "rssi: %.2g dBm ", value); ++ if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "ecio: %.2g dBm ", value); ++ } ++ ++ signal = mm_modem_signal_peek_evdo (modem_signal); ++ if (signal) ++ { ++ if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "rssi: %.2g dBm ", value); ++ if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "ecio: %.2g dBm ", value); ++ if ((value = mm_signal_get_sinr (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "sinr: %.2g dB ", value); ++ if ((value = mm_signal_get_io (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "io: %.2g dBm ", value); ++ } ++ ++ signal = mm_modem_signal_peek_gsm (modem_signal); ++ if (signal) ++ if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "rssi: %.2g dBm ", value); ++ ++ signal = mm_modem_signal_peek_umts (modem_signal); ++ if (signal) ++ { ++ if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "rssi: %.2g dBm ", value); ++ if ((value = mm_signal_get_rscp (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "rscp: %.2g dBm ", value); ++ if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "ecio: %.2g dBm ", value); ++ } ++ ++ signal = mm_modem_signal_peek_lte (modem_signal); ++ if (signal) ++ { ++ if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "rssi: %.2g dBm ", value); ++ if ((value = mm_signal_get_rsrq (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "rsrq: %.2g dB ", value); ++ if ((value = mm_signal_get_rsrp (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "rsrp: %.2g dBm ", value); ++ if ((value = mm_signal_get_snr (signal)) != MM_SIGNAL_UNKNOWN) ++ g_string_append_printf (str, "snr: %.2g dB ", value); ++ } ++ ++ return g_string_free (str, FALSE); ++} ++ ++const gchar * ++cc_wwan_device_get_manufacturer (CcWwanDevice *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ ++ return mm_modem_get_manufacturer (self->modem); ++} ++ ++const gchar * ++cc_wwan_device_get_model (CcWwanDevice *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ ++ return mm_modem_get_model (self->modem); ++} ++ ++const gchar * ++cc_wwan_device_get_firmware_version (CcWwanDevice *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ ++ return mm_modem_get_revision (self->modem); ++} ++ ++const gchar * ++cc_wwan_device_get_identifier (CcWwanDevice *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ ++ return mm_modem_get_equipment_identifier (self->modem); ++} ++ ++const gchar * ++cc_wwan_device_get_simple_error (CcWwanDevice *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ ++ if (!self->error) ++ return NULL; ++ ++ return cc_wwan_error_get_message (self->error); ++} ++ ++gboolean ++cc_wwan_device_is_nm_device (CcWwanDevice *self, ++ GObject *nm_device) ++{ ++#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) ++ g_return_val_if_fail (NM_IS_DEVICE (nm_device), FALSE); ++ ++ return g_str_equal (mm_modem_get_primary_port (self->modem), ++ nm_device_get_iface (NM_DEVICE (nm_device))); ++#else ++ return FALSE; ++#endif ++} ++ ++const gchar * ++cc_wwan_device_get_path (CcWwanDevice *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), ""); ++ ++ return mm_object_get_path (self->mm_object); ++} ++ ++CcWwanData * ++cc_wwan_device_get_data (CcWwanDevice *self) ++{ ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); ++ ++ return self->wwan_data; ++} ++ ++gboolean ++cc_wwan_device_pin_valid (const gchar *password, ++ MMModemLock lock) ++{ ++ size_t len; ++ ++ g_return_val_if_fail (lock == MM_MODEM_LOCK_SIM_PIN || ++ lock == MM_MODEM_LOCK_SIM_PIN2 || ++ lock == MM_MODEM_LOCK_SIM_PUK || ++ lock == MM_MODEM_LOCK_SIM_PUK2, FALSE); ++ if (!password) ++ return FALSE; ++ ++ len = strlen (password); ++ ++ if (len < 4 || len > 8) ++ return FALSE; ++ ++ if (strspn (password, "0123456789") != len) ++ return FALSE; ++ ++ /* ++ * XXX: Can PUK code be something other than 8 digits? ++ * 3GPP standard seems mum on this ++ */ ++ if (lock == MM_MODEM_LOCK_SIM_PUK || ++ lock == MM_MODEM_LOCK_SIM_PUK2) ++ if (len != 8) ++ return FALSE; ++ ++ return TRUE; ++} +diff --git a/panels/wwan/cc-wwan-device.h b/panels/wwan/cc-wwan-device.h +new file mode 100644 +index 000000000..e484bcf30 +--- /dev/null ++++ b/panels/wwan/cc-wwan-device.h +@@ -0,0 +1,152 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-device.h ++ * ++ * Copyright 2019-2020 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK) ++# include "cc-wwan-data.h" ++#endif ++ ++G_BEGIN_DECLS ++ ++typedef enum ++{ ++ CC_WWAN_REGISTRATION_STATE_UNKNOWN, ++ CC_WWAN_REGISTRATION_STATE_IDLE, ++ CC_WWAN_REGISTRATION_STATE_REGISTERED, ++ CC_WWAN_REGISTRATION_STATE_ROAMING, ++ CC_WWAN_REGISTRATION_STATE_SEARCHING, ++ CC_WWAN_REGISTRATION_STATE_DENIED ++} CcWwanState; ++ ++typedef struct _CcWwanData CcWwanData; ++ ++#define CC_TYPE_WWAN_DEVICE (cc_wwan_device_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanDevice, cc_wwan_device, CC, WWAN_DEVICE, GObject) ++ ++CcWwanDevice *cc_wwan_device_new (MMObject *mm_object, ++ GObject *nm_client); ++gboolean cc_wwan_device_has_sim (CcWwanDevice *self); ++MMModemLock cc_wwan_device_get_lock (CcWwanDevice *self); ++gboolean cc_wwan_device_get_sim_lock (CcWwanDevice *self); ++guint cc_wwan_device_get_unlock_retries (CcWwanDevice *self, ++ MMModemLock lock); ++void cc_wwan_device_enable_pin (CcWwanDevice *self, ++ const gchar *pin, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean cc_wwan_device_enable_pin_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error); ++void cc_wwan_device_disable_pin (CcWwanDevice *self, ++ const gchar *pin, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean cc_wwan_device_disable_pin_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error); ++void cc_wwan_device_send_pin (CcWwanDevice *self, ++ const gchar *pin, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean cc_wwan_device_send_pin_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error); ++void cc_wwan_device_send_puk (CcWwanDevice *self, ++ const gchar *puk, ++ const gchar *pin, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean cc_wwan_device_send_puk_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error); ++void cc_wwan_device_change_pin (CcWwanDevice *self, ++ const gchar *old_pin, ++ const gchar *new_pin, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean cc_wwan_device_change_pin_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error); ++const gchar *cc_wwan_device_get_operator_name (CcWwanDevice *self); ++gchar *cc_wwan_device_dup_own_numbers (CcWwanDevice *self); ++gchar *cc_wwan_device_dup_network_type_string (CcWwanDevice *self); ++gchar *cc_wwan_device_dup_signal_string (CcWwanDevice *self); ++const gchar *cc_wwan_device_get_manufacturer (CcWwanDevice *self); ++const gchar *cc_wwan_device_get_model (CcWwanDevice *self); ++const gchar *cc_wwan_device_get_firmware_version (CcWwanDevice *self); ++const gchar *cc_wwan_device_get_identifier (CcWwanDevice *self); ++gboolean cc_wwan_device_get_current_mode (CcWwanDevice *self, ++ MMModemMode *allowed, ++ MMModemMode *preferred); ++gboolean cc_wwan_device_is_auto_network (CcWwanDevice *self); ++CcWwanState cc_wwan_device_get_network_state (CcWwanDevice *self); ++gboolean cc_wwan_device_get_supported_modes (CcWwanDevice *self, ++ MMModemMode *allowed, ++ MMModemMode *preferred); ++void cc_wwan_device_set_current_mode (CcWwanDevice *self, ++ MMModemMode allowed, ++ MMModemMode preferred, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean cc_wwan_device_set_current_mode_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error); ++gchar *cc_wwan_device_get_string_from_mode (CcWwanDevice *self, ++ MMModemMode allowed, ++ MMModemMode preferred); ++void cc_wwan_device_scan_networks (CcWwanDevice *self, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++GList *cc_wwan_device_scan_networks_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error); ++void cc_wwan_device_register_network (CcWwanDevice *self, ++ const gchar *network_id, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean cc_wwan_device_register_network_finish (CcWwanDevice *self, ++ GAsyncResult *result, ++ GError **error); ++const gchar *cc_wwan_device_get_simple_error (CcWwanDevice *self); ++GSList *cc_wwan_device_get_apn_list (CcWwanDevice *self); ++gboolean cc_wwan_device_is_nm_device (CcWwanDevice *self, ++ GObject *nm_device); ++const gchar *cc_wwan_device_get_path (CcWwanDevice *self); ++CcWwanData *cc_wwan_device_get_data (CcWwanDevice *self); ++gboolean cc_wwan_device_pin_valid (const gchar *password, ++ MMModemLock lock); ++ ++G_END_DECLS +diff --git a/panels/wwan/cc-wwan-errors-private.h b/panels/wwan/cc-wwan-errors-private.h +new file mode 100644 +index 000000000..761b82f35 +--- /dev/null ++++ b/panels/wwan/cc-wwan-errors-private.h +@@ -0,0 +1,104 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-errors-private.h ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * Modified from mm-error-helpers.c from ModemManager ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++typedef struct { ++ guint code; ++ const gchar *message; ++} ErrorTable; ++ ++ ++static ErrorTable me_errors[] = { ++ { MM_MOBILE_EQUIPMENT_ERROR_PHONE_FAILURE, N_("Phone failure") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NO_CONNECTION, N_("No connection to phone") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_LINK_RESERVED, N_("Phone-adaptor link reserved") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED, N_("Operation not allowed") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, N_("Operation not supported") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN, N_("PH-SIM PIN required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN, N_("PH-FSIM PIN required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK, N_("PH-FSIM PUK required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED, N_("SIM not inserted") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN, N_("SIM PIN required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK, N_("SIM PUK required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE, N_("SIM failure") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SIM_BUSY, N_("SIM busy") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG, N_("SIM wrong") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD, N_("Incorrect password") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN2, N_("SIM PIN2 required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK2, N_("SIM PUK2 required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FULL, N_("Memory full") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_INVALID_INDEX, N_("Invalid index") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NOT_FOUND, N_("Not found") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FAILURE, N_("Memory failure") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK, N_("No network service") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, N_("Network timeout") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED, N_("Network not allowed - emergency calls only") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN, N_("Network personalization PIN required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK, N_("Network personalization PUK required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN, N_("Network subset personalization PIN required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK, N_("Network subset personalization PUK required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN, N_("Service provider personalization PIN required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK, N_("Service provider personalization PUK required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN, N_("Corporate personalization PIN required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK, N_("Corporate personalization PUK required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, N_("Unknown error") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_MS, N_("Illegal MS") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_ME, N_("Illegal ME") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_NOT_ALLOWED, N_("GPRS services not allowed") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PLMN_NOT_ALLOWED, N_("PLMN not allowed") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_LOCATION_NOT_ALLOWED, N_("Location area not allowed") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ROAMING_NOT_ALLOWED, N_("Roaming not allowed in this location area") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUPPORTED, N_("Service option not supported") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUBSCRIBED, N_("Requested service option not subscribed") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_OUT_OF_ORDER, N_("Service option temporarily out of order") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_UNKNOWN, N_("Unspecified GPRS error") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PDP_AUTH_FAILURE, N_("PDP authentication failure") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_INVALID_MOBILE_CLASS, N_("Invalid mobile class") }, ++}; ++ ++static inline const gchar * ++cc_wwan_error_get_message (GError *error) ++{ ++ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ return _("Action Cancelled"); ++ ++ if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED)) ++ return _("Access denied"); ++ ++ if (error->domain != MM_MOBILE_EQUIPMENT_ERROR) ++ return error->message; ++ ++ for (guint i = 0; i < G_N_ELEMENTS (me_errors); i++) ++ if (me_errors[i].code == error->code) ++ return _(me_errors[i].message); ++ ++ return _("Unknown Error"); ++} +diff --git a/panels/wwan/cc-wwan-mode-dialog.c b/panels/wwan/cc-wwan-mode-dialog.c +new file mode 100644 +index 000000000..e5917a41c +--- /dev/null ++++ b/panels/wwan/cc-wwan-mode-dialog.c +@@ -0,0 +1,327 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-mode-dialog.c ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-network-mode-dialog" ++ ++#include ++#include ++#include ++ ++#include "list-box-helper.h" ++#include "cc-wwan-mode-dialog.h" ++#include "cc-wwan-resources.h" ++ ++/** ++ * @short_description: WWAN network type selection dialog ++ */ ++ ++#define CC_TYPE_WWAN_MODE_ROW (cc_wwan_mode_row_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanModeRow, cc_wwan_mode_row, CC, WWAN_MODE_ROW, GtkListBoxRow) ++ ++struct _CcWwanModeDialog ++{ ++ GtkDialog parent_instance; ++ ++ CcWwanDevice *device; ++ GtkListBox *network_mode_list; ++ CcWwanModeRow *selected_row; ++ ++ MMModemMode preferred; ++ MMModemMode allowed; ++ MMModemMode new_allowed; ++ MMModemMode new_preferred; ++}; ++ ++G_DEFINE_TYPE (CcWwanModeDialog, cc_wwan_mode_dialog, GTK_TYPE_DIALOG) ++ ++ ++enum { ++ PROP_0, ++ PROP_DEVICE, ++ N_PROPS ++}; ++ ++static GParamSpec *properties[N_PROPS]; ++ ++struct _CcWwanModeRow ++{ ++ GtkListBoxRow parent_instance; ++ GtkImage *ok_emblem; ++ MMModemMode allowed; ++ MMModemMode preferred; ++}; ++ ++G_DEFINE_TYPE (CcWwanModeRow, cc_wwan_mode_row, GTK_TYPE_LIST_BOX_ROW) ++ ++static void ++cc_wwan_mode_row_class_init (CcWwanModeRowClass *klass) ++{ ++} ++ ++static void ++cc_wwan_mode_row_init (CcWwanModeRow *row) ++{ ++} ++ ++static void ++cc_wwan_mode_changed_cb (CcWwanModeDialog *self, ++ CcWwanModeRow *row) ++{ ++ g_assert (CC_IS_WWAN_MODE_DIALOG (self)); ++ g_assert (CC_IS_WWAN_MODE_ROW (row)); ++ ++ if (row == self->selected_row) ++ return; ++ ++ gtk_widget_show (GTK_WIDGET (row->ok_emblem)); ++ ++ if (self->selected_row) ++ gtk_widget_hide (GTK_WIDGET (self->selected_row->ok_emblem)); ++ ++ self->selected_row = row; ++} ++ ++static void ++cc_wwan_mode_dialog_ok_clicked_cb (CcWwanModeDialog *self) ++{ ++ g_assert (CC_IS_WWAN_MODE_DIALOG (self)); ++ ++ if (self->selected_row) ++ { ++ cc_wwan_device_set_current_mode (self->device, ++ self->selected_row->allowed, ++ self->selected_row->preferred, ++ NULL, NULL, NULL); ++ } ++ else ++ { ++ g_return_if_reached (); ++ } ++ ++ gtk_widget_hide (GTK_WIDGET (self)); ++} ++ ++static GtkWidget * ++cc_wwan_mode_dialog_row_new (CcWwanModeDialog *self, ++ MMModemMode allowed, ++ MMModemMode preferred) ++{ ++ CcWwanModeRow *row; ++ GtkWidget *box, *label, *image; ++ g_autofree gchar *mode = NULL; ++ ++ g_assert (CC_WWAN_MODE_DIALOG (self)); ++ ++ row = g_object_new (CC_TYPE_WWAN_MODE_ROW, NULL); ++ row->allowed = allowed; ++ row->preferred = preferred; ++ ++ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); ++ gtk_widget_show (box); ++ g_object_set (box, "margin", 18, NULL); ++ gtk_container_add (GTK_CONTAINER (row), box); ++ ++ mode = cc_wwan_device_get_string_from_mode (self->device, allowed, preferred); ++ label = gtk_label_new (mode); ++ gtk_widget_show (label); ++ gtk_widget_set_hexpand (label, TRUE); ++ gtk_widget_set_halign (label, GTK_ALIGN_START); ++ gtk_container_add (GTK_CONTAINER (box), label); ++ ++ /* image should be hidden by default */ ++ image = gtk_image_new_from_icon_name ("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON); ++ gtk_container_add (GTK_CONTAINER (box), image); ++ row->ok_emblem = GTK_IMAGE (image); ++ ++ return GTK_WIDGET (row); ++} ++ ++static void ++cc_wwan_mode_dialog_update (CcWwanModeDialog *self) ++{ ++ MMModemMode allowed; ++ MMModemMode modes[][2] = { ++ {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G}, ++ {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, 0}, ++ {MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G}, ++ {MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, 0}, ++ {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G}, ++ {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, 0}, ++ {MM_MODEM_MODE_4G, 0}, ++ {MM_MODEM_MODE_3G, 0}, ++ {MM_MODEM_MODE_2G, 0}, ++ }; ++ size_t i; ++ ++ g_assert (CC_IS_WWAN_MODE_DIALOG (self)); ++ ++ if (!cc_wwan_device_get_supported_modes (self->device, &allowed, NULL)) ++ { ++ g_warning ("No modes supported by modem"); ++ return; ++ } ++ ++ for (i = 0; i < G_N_ELEMENTS (modes); i++) ++ { ++ GtkWidget *row; ++ ++ if ((modes[i][0] & allowed) != modes[i][0]) ++ continue; ++ ++ if (modes[i][1] && !(modes[i][1] & allowed)) ++ continue; ++ ++ row = cc_wwan_mode_dialog_row_new (self, modes[i][0], modes[i][1]); ++ gtk_widget_show (row); ++ gtk_container_add (GTK_CONTAINER (self->network_mode_list), row); ++ } ++} ++ ++static void ++cc_wwan_mode_dialog_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (object); ++ ++ switch (prop_id) ++ { ++ case PROP_DEVICE: ++ self->device = g_value_dup_object (value); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++cc_wwan_mode_dialog_constructed (GObject *object) ++{ ++ CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (object); ++ ++ G_OBJECT_CLASS (cc_wwan_mode_dialog_parent_class)->constructed (object); ++ ++ if(!cc_wwan_device_get_current_mode (self->device, &self->allowed, &self->preferred)) ++ g_warning ("Can't get allowed and preferred wwan modes"); ++ ++ cc_wwan_mode_dialog_update (self); ++} ++ ++static void ++cc_wwan_mode_dialog_dispose (GObject *object) ++{ ++ CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (object); ++ ++ g_clear_object (&self->device); ++ ++ G_OBJECT_CLASS (cc_wwan_mode_dialog_parent_class)->dispose (object); ++} ++ ++static void ++cc_wwan_mode_dialog_update_mode (CcWwanModeRow *row, ++ CcWwanModeDialog *self) ++{ ++ if (self->allowed == row->allowed && self->preferred == row->preferred) ++ { ++ self->selected_row = row; ++ gtk_widget_show (GTK_WIDGET (row->ok_emblem)); ++ } ++ else ++ gtk_widget_hide (GTK_WIDGET (row->ok_emblem)); ++} ++ ++static void ++cc_wwan_mode_dialog_show (GtkWidget *widget) ++{ ++ CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (widget); ++ ++ if(!cc_wwan_device_get_current_mode (self->device, &self->allowed, &self->preferred)) ++ { ++ g_warning ("Can't get allowed and preferred wwan modes"); ++ goto end; ++ } ++ ++ gtk_container_foreach (GTK_CONTAINER (self->network_mode_list), ++ (GtkCallback)cc_wwan_mode_dialog_update_mode, ++ self); ++ end: ++ GTK_WIDGET_CLASS (cc_wwan_mode_dialog_parent_class)->show (widget); ++} ++ ++static void ++cc_wwan_mode_dialog_class_init (CcWwanModeDialogClass *klass) ++{ ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->set_property = cc_wwan_mode_dialog_set_property; ++ object_class->constructed = cc_wwan_mode_dialog_constructed; ++ object_class->dispose = cc_wwan_mode_dialog_dispose; ++ ++ widget_class->show = cc_wwan_mode_dialog_show; ++ ++ properties[PROP_DEVICE] = ++ g_param_spec_object ("device", ++ "Device", ++ "The WWAN Device", ++ CC_TYPE_WWAN_DEVICE, ++ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++ ++ gtk_widget_class_set_template_from_resource (widget_class, ++ "/org/gnome/control-center/wwan/cc-wwan-mode-dialog.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, CcWwanModeDialog, network_mode_list); ++ ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_mode_changed_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_mode_dialog_ok_clicked_cb); ++} ++ ++static void ++cc_wwan_mode_dialog_init (CcWwanModeDialog *self) ++{ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++ ++ gtk_list_box_set_header_func (self->network_mode_list, ++ cc_list_box_update_header_func, ++ NULL, NULL); ++} ++ ++CcWwanModeDialog * ++cc_wwan_mode_dialog_new (GtkWindow *parent_window, ++ CcWwanDevice *device) ++{ ++ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL); ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL); ++ ++ return g_object_new (CC_TYPE_WWAN_MODE_DIALOG, ++ "transient-for", parent_window, ++ "use-header-bar", 1, ++ "device", device, ++ NULL); ++} +diff --git a/panels/wwan/cc-wwan-mode-dialog.h b/panels/wwan/cc-wwan-mode-dialog.h +new file mode 100644 +index 000000000..2399f0b7b +--- /dev/null ++++ b/panels/wwan/cc-wwan-mode-dialog.h +@@ -0,0 +1,40 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-mode-dialog.h ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#include "cc-wwan-device.h" ++ ++G_BEGIN_DECLS ++ ++#define CC_TYPE_WWAN_MODE_DIALOG (cc_wwan_mode_dialog_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanModeDialog, cc_wwan_mode_dialog, CC, WWAN_MODE_DIALOG, GtkDialog) ++ ++CcWwanModeDialog *cc_wwan_mode_dialog_new (GtkWindow *parent_window, ++ CcWwanDevice *device); ++ ++G_END_DECLS +diff --git a/panels/wwan/cc-wwan-mode-dialog.ui b/panels/wwan/cc-wwan-mode-dialog.ui +new file mode 100644 +index 000000000..e0a924a39 +--- /dev/null ++++ b/panels/wwan/cc-wwan-mode-dialog.ui +@@ -0,0 +1,57 @@ ++ ++ ++ ++ +diff --git a/panels/wwan/cc-wwan-network-dialog.c b/panels/wwan/cc-wwan-network-dialog.c +new file mode 100644 +index 000000000..1c8883b88 +--- /dev/null ++++ b/panels/wwan/cc-wwan-network-dialog.c +@@ -0,0 +1,443 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-network-dialog.c ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-network-dialog" ++ ++#include ++#include ++#include ++ ++#include "list-box-helper.h" ++#include "cc-list-row.h" ++#include "cc-wwan-errors-private.h" ++#include "cc-wwan-network-dialog.h" ++#include "cc-wwan-resources.h" ++ ++/** ++ * @short_description: WWAN network operator selection dialog ++ */ ++ ++#define CC_TYPE_WWAN_NETWORK_ROW (cc_wwan_network_row_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanNetworkRow, cc_wwan_network_row, CC, WWAN_NETWORK_ROW, GtkListBoxRow) ++ ++struct _CcWwanNetworkDialog ++{ ++ GtkDialog parent_instance; ++ ++ CcListRow *automatic_row; ++ GtkButton *button_apply; ++ GtkSpinner *loading_spinner; ++ GtkBox *network_search_title; ++ GtkLabel *notification_label; ++ GtkRevealer *notification_revealer; ++ GtkListBox *operator_list_box; ++ GtkButton *refresh_button; ++ ++ CcWwanDevice *device; ++ GList *operator_list; ++ ++ CcWwanNetworkRow *selected_row; ++ ++ GCancellable *search_cancellable; ++ ++ guint revealer_timeout_id; ++ gboolean no_update_network; ++}; ++ ++G_DEFINE_TYPE (CcWwanNetworkDialog, cc_wwan_network_dialog, GTK_TYPE_DIALOG) ++ ++ ++enum { ++ PROP_0, ++ PROP_DEVICE, ++ N_PROPS ++}; ++ ++static GParamSpec *properties[N_PROPS]; ++ ++struct _CcWwanNetworkRow ++{ ++ GtkListBoxRow parent_instance; ++ GtkImage *ok_emblem; ++ gchar *operator_code; ++}; ++ ++G_DEFINE_TYPE (CcWwanNetworkRow, cc_wwan_network_row, GTK_TYPE_LIST_BOX_ROW) ++ ++static void ++cc_wwan_network_row_finalize (GObject *object) ++{ ++ CcWwanNetworkRow *row = (CcWwanNetworkRow *)object; ++ ++ g_free (row->operator_code); ++ ++ G_OBJECT_CLASS (cc_wwan_network_row_parent_class)->finalize (object); ++} ++ ++static void ++cc_wwan_network_row_class_init (CcWwanNetworkRowClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = cc_wwan_network_row_finalize; ++} ++ ++static void ++cc_wwan_network_row_init (CcWwanNetworkRow *row) ++{ ++} ++ ++static void ++cc_wwan_on_notification_closed (CcWwanNetworkDialog *self, ++ GtkWidget *button) ++{ ++ g_assert (CC_IS_WWAN_NETWORK_DIALOG (self)); ++ ++ gtk_revealer_set_reveal_child (GTK_REVEALER (self->notification_revealer), FALSE); ++ ++ if (self->revealer_timeout_id != 0) ++ g_source_remove (self->revealer_timeout_id); ++ ++ self->revealer_timeout_id = 0; ++} ++ ++static gboolean ++cc_wwan_on_notification_timeout (gpointer user_data) ++{ ++ cc_wwan_on_notification_closed (user_data, NULL); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static void ++cc_wwan_network_changed_cb (CcWwanNetworkDialog *self, ++ CcWwanNetworkRow *row) ++{ ++ if (row == self->selected_row) ++ return; ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->button_apply), TRUE); ++ gtk_widget_show (GTK_WIDGET (row->ok_emblem)); ++ ++ if (self->selected_row) ++ gtk_widget_hide (GTK_WIDGET (self->selected_row->ok_emblem)); ++ ++ self->selected_row = row; ++} ++ ++/* ++ * cc_wwan_network_dialog_row_new: ++ * @self: a #CcWwanNetworkDialog ++ * @operator_name: (transfer full): The long operator name ++ * @operator_id: (transfer full): operator id ++ */ ++static CcWwanNetworkRow * ++cc_wwan_network_dialog_row_new (CcWwanNetworkDialog *self, ++ const gchar *operator_name, ++ const gchar *operator_code) ++{ ++ CcWwanNetworkRow *row; ++ GtkWidget *box, *label, *image; ++ ++ row = g_object_new (CC_TYPE_WWAN_NETWORK_ROW, NULL); ++ ++ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); ++ gtk_widget_show (box); ++ g_object_set (box, "margin", 18, NULL); ++ gtk_container_add (GTK_CONTAINER (row), box); ++ ++ label = gtk_label_new (operator_name); ++ gtk_widget_show (label); ++ gtk_widget_set_hexpand (label, TRUE); ++ gtk_widget_set_halign (label, GTK_ALIGN_START); ++ gtk_container_add (GTK_CONTAINER (box), label); ++ ++ image = gtk_image_new_from_icon_name ("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON); ++ row->ok_emblem = GTK_IMAGE (image); ++ gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (row->ok_emblem)); ++ ++ row->operator_code = g_strdup (operator_code); ++ ++ return row; ++} ++ ++static void ++cc_wwan_network_dialog_update_current_network (CcWwanNetworkDialog *self) ++{ ++ CcWwanNetworkRow *row; ++ const gchar *operator_name; ++ ++ operator_name = cc_wwan_device_get_operator_name (self->device); ++ ++ if (!operator_name || operator_name[0] == '\0') ++ return; ++ ++ gtk_container_foreach (GTK_CONTAINER (self->operator_list_box), ++ (GtkCallback)gtk_widget_destroy, NULL); ++ ++ row = cc_wwan_network_dialog_row_new (self, operator_name, ""); ++ self->selected_row = row; ++ gtk_container_add (GTK_CONTAINER (self->operator_list_box), GTK_WIDGET (row)); ++ gtk_widget_show_all (GTK_WIDGET (self->operator_list_box)); ++} ++ ++static void ++cc_wwan_network_dialog_update (CcWwanNetworkDialog *self) ++{ ++ CcWwanNetworkRow *row; ++ GList *item; ++ const gchar *operator_code, *operator_name; ++ ++ gtk_container_foreach (GTK_CONTAINER (self->operator_list_box), ++ (GtkCallback)gtk_widget_destroy, NULL); ++ ++ for (item = self->operator_list; item; item = item->next) ++ { ++ operator_code = mm_modem_3gpp_network_get_operator_code (item->data); ++ operator_name = mm_modem_3gpp_network_get_operator_long (item->data); ++ ++ row = cc_wwan_network_dialog_row_new (self, operator_name, operator_code); ++ gtk_widget_show (GTK_WIDGET (row)); ++ gtk_container_add (GTK_CONTAINER (self->operator_list_box), GTK_WIDGET (row)); ++ } ++} ++ ++static void ++cc_wwan_network_scan_complete_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr(CcWwanNetworkDialog) self = user_data; ++ g_autoptr(GError) error = NULL; ++ ++ if (self->operator_list) ++ g_list_free_full (self->operator_list, (GDestroyNotify)mm_modem_3gpp_network_free); ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->refresh_button), TRUE); ++ gtk_spinner_stop (self->loading_spinner); ++ self->operator_list = cc_wwan_device_scan_networks_finish (self->device, result, &error); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->operator_list_box), !error); ++ ++ if (!error) ++ { ++ cc_wwan_network_dialog_update (self); ++ gtk_widget_show (GTK_WIDGET (self->operator_list_box)); ++ } ++ else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ { ++ self->no_update_network = TRUE; ++ gtk_widget_activate (GTK_WIDGET (self->automatic_row)); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->operator_list_box), FALSE); ++ ++ gtk_label_set_label (self->notification_label, ++ cc_wwan_error_get_message (error)); ++ gtk_revealer_set_reveal_child (self->notification_revealer, TRUE); ++ self->revealer_timeout_id = g_timeout_add_seconds (5, cc_wwan_on_notification_timeout, self); ++ ++ gtk_widget_show (GTK_WIDGET (self->operator_list_box)); ++ g_warning ("Error: scanning networks failed: %s", error->message); ++ } ++} ++ ++static void ++cc_wwan_network_dialog_refresh_networks (CcWwanNetworkDialog *self) ++{ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->refresh_button), FALSE); ++ gtk_spinner_start (self->loading_spinner); ++ cc_wwan_device_scan_networks (self->device, self->search_cancellable, ++ (GAsyncReadyCallback)cc_wwan_network_scan_complete_cb, ++ g_object_ref (self)); ++} ++ ++static void ++cc_wwan_network_dialog_apply_clicked_cb (CcWwanNetworkDialog *self) ++{ ++ gboolean is_auto; ++ ++ g_assert (CC_IS_WWAN_NETWORK_DIALOG (self)); ++ ++ is_auto = cc_list_row_get_active (self->automatic_row); ++ ++ if (is_auto) ++ cc_wwan_device_register_network (self->device, "", NULL, NULL, NULL); ++ else if (self->selected_row) ++ cc_wwan_device_register_network (self->device, self->selected_row->operator_code, NULL, NULL, self); ++ else ++ g_warn_if_reached (); ++ ++ gtk_widget_hide (GTK_WIDGET (self)); ++} ++ ++static void ++cc_wwan_auto_network_changed_cb (CcWwanNetworkDialog *self, ++ GParamSpec *pspec, ++ CcListRow *auto_network_row) ++{ ++ gboolean is_auto; ++ ++ g_assert (CC_IS_WWAN_NETWORK_DIALOG (self)); ++ g_assert (CC_IS_LIST_ROW (auto_network_row)); ++ ++ is_auto = cc_list_row_get_active (auto_network_row); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->button_apply), is_auto); ++ ++ if (self->no_update_network) ++ { ++ self->no_update_network = FALSE; ++ return; ++ } ++ ++ self->selected_row = NULL; ++ gtk_widget_set_visible (GTK_WIDGET (self->network_search_title), !is_auto); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->operator_list_box), !is_auto); ++ gtk_widget_hide (GTK_WIDGET (self->operator_list_box)); ++ ++ if (is_auto) ++ { ++ g_cancellable_cancel (self->search_cancellable); ++ g_cancellable_reset (self->search_cancellable); ++ } ++ else ++ { ++ cc_wwan_network_dialog_refresh_networks (self); ++ } ++} ++ ++static void ++cc_wwan_network_dialog_show (GtkWidget *widget) ++{ ++ CcWwanNetworkDialog *self = (CcWwanNetworkDialog *)widget; ++ gboolean is_auto; ++ ++ is_auto = cc_wwan_device_is_auto_network (self->device); ++ ++ g_object_set (self->automatic_row, "active", is_auto, NULL); ++ ++ cc_wwan_network_dialog_update_current_network (self); ++ ++ GTK_WIDGET_CLASS (cc_wwan_network_dialog_parent_class)->show (widget); ++} ++ ++static void ++cc_wwan_network_dialog_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ CcWwanNetworkDialog *self = (CcWwanNetworkDialog *)object; ++ ++ switch (prop_id) ++ { ++ case PROP_DEVICE: ++ self->device = g_value_dup_object (value); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++cc_wwan_network_dialog_dispose (GObject *object) ++{ ++ CcWwanNetworkDialog *self = (CcWwanNetworkDialog *)object; ++ ++ if (self->revealer_timeout_id != 0) ++ g_source_remove (self->revealer_timeout_id); ++ ++ self->revealer_timeout_id = 0; ++ ++ g_cancellable_cancel (self->search_cancellable); ++ ++ g_clear_object (&self->search_cancellable); ++ g_clear_object (&self->device); ++ ++ G_OBJECT_CLASS (cc_wwan_network_dialog_parent_class)->dispose (object); ++} ++ ++static void ++cc_wwan_network_dialog_class_init (CcWwanNetworkDialogClass *klass) ++{ ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->set_property = cc_wwan_network_dialog_set_property; ++ object_class->dispose = cc_wwan_network_dialog_dispose; ++ ++ widget_class->show = cc_wwan_network_dialog_show; ++ ++ properties[PROP_DEVICE] = ++ g_param_spec_object ("device", ++ "Device", ++ "The WWAN Device", ++ CC_TYPE_WWAN_DEVICE, ++ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++ ++ gtk_widget_class_set_template_from_resource (widget_class, ++ "/org/gnome/control-center/wwan/cc-wwan-network-dialog.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, automatic_row); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, button_apply); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, loading_spinner); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, network_search_title); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, notification_label); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, notification_revealer); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, operator_list_box); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, refresh_button); ++ ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_network_changed_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_on_notification_closed); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_auto_network_changed_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_network_dialog_refresh_networks); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_network_dialog_apply_clicked_cb); ++} ++ ++static void ++cc_wwan_network_dialog_init (CcWwanNetworkDialog *self) ++{ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++ ++ self->search_cancellable = g_cancellable_new (); ++ ++ gtk_list_box_set_header_func (self->operator_list_box, ++ cc_list_box_update_header_func, ++ NULL, NULL); ++} ++ ++CcWwanNetworkDialog * ++cc_wwan_network_dialog_new (GtkWindow *parent_window, ++ CcWwanDevice *device) ++{ ++ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL); ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL); ++ ++ return g_object_new (CC_TYPE_WWAN_NETWORK_DIALOG, ++ "transient-for", parent_window, ++ "use-header-bar", 1, ++ "device", device, ++ NULL); ++} +diff --git a/panels/wwan/cc-wwan-network-dialog.h b/panels/wwan/cc-wwan-network-dialog.h +new file mode 100644 +index 000000000..1818a0876 +--- /dev/null ++++ b/panels/wwan/cc-wwan-network-dialog.h +@@ -0,0 +1,40 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-network-dialog.h ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#include "cc-wwan-device.h" ++ ++G_BEGIN_DECLS ++ ++#define CC_TYPE_WWAN_NETWORK_DIALOG (cc_wwan_network_dialog_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanNetworkDialog, cc_wwan_network_dialog, CC, WWAN_NETWORK_DIALOG, GtkDialog) ++ ++CcWwanNetworkDialog *cc_wwan_network_dialog_new (GtkWindow *parent_window, ++ CcWwanDevice *device); ++ ++G_END_DECLS +diff --git a/panels/wwan/cc-wwan-network-dialog.ui b/panels/wwan/cc-wwan-network-dialog.ui +new file mode 100644 +index 000000000..03223b333 +--- /dev/null ++++ b/panels/wwan/cc-wwan-network-dialog.ui +@@ -0,0 +1,188 @@ ++ ++ ++ ++ +diff --git a/panels/wwan/cc-wwan-panel.c b/panels/wwan/cc-wwan-panel.c +new file mode 100644 +index 000000000..963c46900 +--- /dev/null ++++ b/panels/wwan/cc-wwan-panel.c +@@ -0,0 +1,929 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-panel.c ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-panel" ++ ++#include ++#include ++#include ++ ++#include "cc-wwan-device.h" ++#include "cc-wwan-data.h" ++#include "cc-wwan-device-page.h" ++#include "cc-wwan-panel.h" ++#include "cc-wwan-resources.h" ++ ++#include "shell/cc-application.h" ++#include "shell/cc-debug.h" ++#include "shell/cc-object-storage.h" ++ ++typedef enum { ++ OPERATION_NULL, ++ OPERATION_SHOW_DEVICE, ++} CmdlineOperation; ++ ++struct _CcWwanPanel ++{ ++ CcPanel parent_instance; ++ ++ GtkListBox *data_select_listbox; ++ GtkPopover *data_select_popover; ++ GtkLabel *data_sim_label; ++ GtkListBox *data_sim_select_listbox; ++ GtkStack *devices_stack; ++ GtkStackSwitcher *devices_switcher; ++ GtkSwitch *enable_switch; ++ GtkStack *main_stack; ++ GtkRevealer *multi_device_revealer; ++ GtkLabel *notification_label; ++ GtkRevealer *notification_revealer; ++ ++ GDBusProxy *rfkill_proxy; ++ MMManager *mm_manager; ++ NMClient *nm_client; ++ ++ /* The default device that will be used for data */ ++ CcWwanDevice *data_device; ++ GListStore *devices; ++ GListStore *data_devices; ++ GCancellable *cancellable; ++ ++ CmdlineOperation arg_operation; ++ char *arg_device; ++ ++ guint revealer_timeout_id; ++}; ++ ++enum { ++ PROP_0, ++ PROP_PARAMETERS ++}; ++ ++G_DEFINE_TYPE (CcWwanPanel, cc_wwan_panel, CC_TYPE_PANEL) ++ ++ ++#define CC_TYPE_DATA_DEVICE_ROW (cc_data_device_row_get_type()) ++G_DECLARE_FINAL_TYPE (CcDataDeviceRow, cc_data_device_row, CC, DATA_DEVICE_ROW, GtkListBoxRow) ++ ++struct _CcDataDeviceRow ++{ ++ GtkListBoxRow parent_instance; ++ ++ GtkImage *ok_emblem; ++ CcWwanDevice *device; ++}; ++ ++G_DEFINE_TYPE (CcDataDeviceRow, cc_data_device_row, GTK_TYPE_LIST_BOX_ROW) ++ ++static void ++cc_data_device_row_class_init (CcDataDeviceRowClass *klass) ++{ ++} ++ ++static void ++cc_data_device_row_init (CcDataDeviceRow *row) ++{ ++} ++ ++static CmdlineOperation ++cmdline_operation_from_string (const gchar *str) ++{ ++ if (g_strcmp0 (str, "show-device") == 0) ++ return OPERATION_SHOW_DEVICE; ++ ++ g_warning ("Invalid additional argument %s", str); ++ return OPERATION_NULL; ++} ++ ++static void ++reset_command_line_args (CcWwanPanel *self) ++{ ++ self->arg_operation = OPERATION_NULL; ++ g_clear_pointer (&self->arg_device, g_free); ++} ++ ++static gboolean ++verify_argv (CcWwanPanel *self, ++ const char **args) ++{ ++ switch (self->arg_operation) ++ { ++ case OPERATION_SHOW_DEVICE: ++ if (self->arg_device == NULL) ++ { ++ g_warning ("Operation %s requires an object path", args[0]); ++ return FALSE; ++ } ++ default: ++ return TRUE; ++ } ++} ++ ++static void ++handle_argv (CcWwanPanel *self) ++{ ++ if (self->arg_operation == OPERATION_SHOW_DEVICE && ++ self->arg_operation) ++ { ++ g_autoptr(GList) pages = NULL; ++ ++ pages = gtk_container_get_children (GTK_CONTAINER (self->devices_stack)); ++ ++ for (GList *page = pages; page; page = page->next) ++ { ++ CcWwanDevice *device; ++ ++ device = cc_wwan_device_page_get_device (page->data); ++ ++ if (g_strcmp0 (cc_wwan_device_get_path (device), self->arg_device) == 0) ++ { ++ gtk_stack_set_visible_child (GTK_STACK (self->devices_stack), page->data); ++ g_debug ("Opening device %s", self->arg_device); ++ reset_command_line_args (self); ++ return; ++ } ++ } ++ } ++} ++ ++static gboolean ++wwan_panel_device_is_supported (GDBusObject *object) ++{ ++ MMObject *mm_object; ++ MMModem *modem; ++ MMModemCapability capability; ++ ++ g_assert (G_IS_DBUS_OBJECT (object)); ++ ++ mm_object = MM_OBJECT (object); ++ modem = mm_object_get_modem (mm_object); ++ capability = mm_modem_get_current_capabilities (modem); ++ ++ /* We Support only GSM/3G/LTE devices */ ++ if (capability & (MM_MODEM_CAPABILITY_GSM_UMTS | ++ MM_MODEM_CAPABILITY_LTE | ++ MM_MODEM_CAPABILITY_LTE_ADVANCED)) ++ return TRUE; ++ ++ return FALSE; ++} ++ ++static gint ++wwan_model_get_item_index (GListModel *model, ++ gpointer item) ++{ ++ guint i, n_items; ++ ++ g_assert (G_IS_LIST_MODEL (model)); ++ g_assert (G_IS_OBJECT (item)); ++ ++ n_items = g_list_model_get_n_items (model); ++ ++ for (i = 0; i < n_items; i++) ++ { ++ g_autoptr(GObject) object = NULL; ++ ++ object = g_list_model_get_item (model, i); ++ ++ if (object == item) ++ return i; ++ } ++ ++ return -1; ++} ++ ++static CcWwanDevice * ++wwan_model_get_item_from_mm_object (GListModel *model, ++ MMObject *mm_object) ++{ ++ const gchar *modem_path, *device_path; ++ guint i, n_items; ++ ++ n_items = g_list_model_get_n_items (model); ++ modem_path = mm_object_get_path (mm_object); ++ ++ for (i = 0; i < n_items; i++) ++ { ++ g_autoptr(CcWwanDevice) device = NULL; ++ ++ device = g_list_model_get_item (model, i); ++ device_path = cc_wwan_device_get_path (device); ++ ++ if (g_str_equal (modem_path, device_path)) ++ return g_steal_pointer (&device); ++ } ++ ++ return NULL; ++} ++ ++static CcDataDeviceRow * ++cc_data_device_row_new (CcWwanDevice *device, ++ CcWwanPanel *self) ++{ ++ CcDataDeviceRow *row; ++ GtkWidget *box, *label, *image; ++ g_autofree gchar *operator = NULL; ++ gint index; ++ ++ row = g_object_new (CC_TYPE_DATA_DEVICE_ROW, NULL); ++ row->device = device; ++ ++ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); ++ gtk_widget_show (box); ++ g_object_set (box, "margin", 12, NULL); ++ gtk_container_add (GTK_CONTAINER (row), box); ++ ++ index = wwan_model_get_item_index (G_LIST_MODEL (self->devices), device); ++ operator = g_strdup_printf ("SIM %d", index + 1); ++ label = gtk_label_new (operator); ++ gtk_widget_show (label); ++ gtk_container_add (GTK_CONTAINER (box), label); ++ ++ image = gtk_image_new_from_icon_name ("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON); ++ row->ok_emblem = GTK_IMAGE (image); ++ gtk_container_add (GTK_CONTAINER (box), image); ++ ++ return row; ++} ++ ++static void ++wwan_notification_close_clicked_cb (CcWwanPanel *self) ++{ ++ gtk_revealer_set_reveal_child (self->notification_revealer, FALSE); ++ ++ if (self->revealer_timeout_id != 0) ++ g_source_remove (self->revealer_timeout_id); ++ ++ self->revealer_timeout_id = 0; ++} ++ ++static void ++wwan_data_selector_clicked_cb (CcWwanPanel *self) ++{ ++ if (gtk_widget_is_visible (GTK_WIDGET (self->data_select_popover))) ++ gtk_popover_popdown (self->data_select_popover); ++ else ++ gtk_popover_popup (self->data_select_popover); ++} ++ ++static void ++cc_wwan_panel_update_data_selection (CcDataDeviceRow *row, ++ CcWwanPanel *self) ++{ ++ if (self->data_device == row->device) ++ { ++ g_autofree gchar *str = NULL; ++ gint i; ++ ++ i = wwan_model_get_item_index (G_LIST_MODEL (self->devices), row->device); ++ g_assert (i >= 0); ++ ++ /* Human index starts from 1 */ ++ str = g_strdup_printf ("SIM %d", i + 1); ++ gtk_label_set_label (self->data_sim_label, str); ++ ++ gtk_widget_show (GTK_WIDGET (row->ok_emblem)); ++ } ++ else ++ { ++ gtk_widget_hide (GTK_WIDGET (row->ok_emblem)); ++ } ++} ++ ++static void ++cc_wwan_data_item_activate_cb (CcWwanPanel *self, ++ CcDataDeviceRow *row) ++{ ++ CcWwanData *data; ++ ++ gtk_popover_popdown (self->data_select_popover); ++ ++ if (row->device == self->data_device) ++ return; ++ ++ /* Set lower priority for previously selected APN */ ++ data = cc_wwan_device_get_data (self->data_device); ++ cc_wwan_data_set_priority (data, CC_WWAN_APN_PRIORITY_LOW); ++ cc_wwan_data_save_settings (data, NULL, NULL, NULL); ++ ++ /* Set high priority for currently selected APN */ ++ data = cc_wwan_device_get_data (row->device); ++ cc_wwan_data_set_priority (data, CC_WWAN_APN_PRIORITY_HIGH); ++ cc_wwan_data_save_settings (data, NULL, NULL, NULL); ++ ++ self->data_device = row->device; ++ gtk_container_foreach (GTK_CONTAINER (self->data_select_listbox), ++ (GtkCallback) cc_wwan_panel_update_data_selection, self); ++} ++ ++static void ++wwan_on_airplane_off_clicked_cb (CcWwanPanel *self) ++{ ++ g_debug ("Airplane Mode Off clicked, disabling airplane mode"); ++ g_dbus_proxy_call (self->rfkill_proxy, ++ "org.freedesktop.DBus.Properties.Set", ++ g_variant_new_parsed ("('org.gnome.SettingsDaemon.Rfkill'," ++ "'AirplaneMode', %v)", ++ g_variant_new_boolean (FALSE)), ++ G_DBUS_CALL_FLAGS_NONE, ++ -1, ++ self->cancellable, ++ NULL, ++ NULL); ++} ++ ++static gboolean ++cc_wwan_panel_get_cached_dbus_property (GDBusProxy *proxy, ++ const gchar *property) ++{ ++ g_autoptr(GVariant) result = NULL; ++ ++ g_assert (G_IS_DBUS_PROXY (proxy)); ++ g_assert (property && *property); ++ ++ result = g_dbus_proxy_get_cached_property (proxy, property); ++ g_assert (!result || g_variant_is_of_type (result, G_VARIANT_TYPE_BOOLEAN)); ++ ++ return result ? g_variant_get_boolean (result) : FALSE; ++} ++ ++static void ++cc_wwan_panel_update_view (CcWwanPanel *self) ++{ ++ gboolean has_airplane, is_airplane = FALSE, enabled = FALSE; ++ ++ has_airplane = cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "HasAirplaneMode"); ++ has_airplane &= cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "ShouldShowAirplaneMode"); ++ ++ if (has_airplane) ++ { ++ is_airplane = cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "AirplaneMode"); ++ is_airplane |= cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "HardwareAirplaneMode"); ++ } ++ ++ if (self->nm_client) ++ enabled = nm_client_wwan_get_enabled (self->nm_client); ++ ++ if (has_airplane && is_airplane) ++ gtk_stack_set_visible_child_name (self->main_stack, "airplane-mode"); ++ else if (enabled && g_list_model_get_n_items (G_LIST_MODEL (self->devices)) > 0) ++ gtk_stack_set_visible_child_name (self->main_stack, "device-settings"); ++ else ++ gtk_stack_set_visible_child_name (self->main_stack, "no-wwan-devices"); ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->enable_switch), !is_airplane); ++ ++ if (enabled) ++ gtk_revealer_set_reveal_child (self->multi_device_revealer, ++ g_list_model_get_n_items (G_LIST_MODEL (self->devices)) > 1); ++} ++ ++static void ++cc_wwan_panel_on_notification_closed (CcWwanPanel *self, ++ GtkWidget *button) ++{ ++ gtk_revealer_set_reveal_child (self->notification_revealer, FALSE); ++ ++ if (self->revealer_timeout_id != 0) ++ g_source_remove (self->revealer_timeout_id); ++ ++ self->revealer_timeout_id = 0; ++} ++ ++static gboolean ++cc_wwan_panel_on_notification_timeout (gpointer user_data) ++{ ++ cc_wwan_panel_on_notification_closed (user_data, NULL); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static void ++cc_wwan_panel_notification_changed_cb (CcWwanPanel *self) ++{ ++ const gchar *label; ++ ++ label = gtk_label_get_label (self->notification_label); ++ ++ if (label && *label) ++ { ++ gtk_revealer_set_reveal_child (self->notification_revealer, TRUE); ++ self->revealer_timeout_id = g_timeout_add_seconds (5, cc_wwan_panel_on_notification_timeout, self); ++ } ++ else ++ { ++ cc_wwan_panel_on_notification_closed (self, NULL); ++ } ++} ++ ++static void ++cc_wwan_panel_add_device (CcWwanPanel *self, ++ CcWwanDevice *device) ++{ ++ CcWwanDevicePage *device_page; ++ g_autofree gchar *operator_name = NULL; ++ g_autofree gchar *stack_name = NULL; ++ guint n_items; ++ ++ g_list_store_append (self->devices, device); ++ ++ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->devices)); ++ operator_name = g_strdup_printf (_("SIM %d"), n_items); ++ stack_name = g_strdup_printf ("sim-%d", n_items); ++ ++ device_page = cc_wwan_device_page_new (device, GTK_WIDGET (self->notification_label)); ++ cc_wwan_device_page_set_sim_index (device_page, n_items); ++ gtk_stack_add_titled (self->devices_stack, ++ GTK_WIDGET (device_page), stack_name, operator_name); ++} ++ ++static void ++cc_wwan_panel_update_page_title (CcWwanDevicePage *device_page, ++ CcWwanPanel *self) ++{ ++ g_autofree gchar *title = NULL; ++ g_autofree gchar *name = NULL; ++ CcWwanDevice *device; ++ GtkWidget *parent; ++ gint index; ++ ++ device = cc_wwan_device_page_get_device (device_page); ++ ++ parent = gtk_widget_get_parent (GTK_WIDGET (device_page)); ++ index = wwan_model_get_item_index (G_LIST_MODEL (self->devices), device); ++ ++ if (index == -1) ++ g_return_if_reached (); ++ ++ /* index starts with 0, but we need human readable index to be 1+ */ ++ cc_wwan_device_page_set_sim_index (device_page, index + 1); ++ title = g_strdup_printf (_("SIM %d"), index + 1); ++ name = g_strdup_printf ("sim-%d", index + 1); ++ gtk_container_child_set (GTK_CONTAINER (parent), ++ GTK_WIDGET (device_page), ++ "title", title, ++ "name", name, ++ NULL); ++} ++ ++static void ++cc_wwan_panel_remove_mm_object (CcWwanPanel *self, ++ MMObject *mm_object) ++{ ++ g_autoptr(CcWwanDevice) device = NULL; ++ GtkWidget *device_page; ++ g_autofree gchar *stack_name = NULL; ++ guint n_items; ++ gint index; ++ ++ device = wwan_model_get_item_from_mm_object (G_LIST_MODEL (self->devices), mm_object); ++ ++ if (!device) ++ return; ++ ++ index = wwan_model_get_item_index (G_LIST_MODEL (self->data_devices), device); ++ if (index != -1) ++ g_list_store_remove (self->data_devices, index); ++ ++ index = wwan_model_get_item_index (G_LIST_MODEL (self->devices), device); ++ if (index == -1) ++ return; ++ ++ g_list_store_remove (self->devices, index); ++ stack_name = g_strdup_printf ("sim-%d", index + 1); ++ device_page = gtk_stack_get_child_by_name (self->devices_stack, stack_name); ++ gtk_container_remove (GTK_CONTAINER (self->devices_stack), device_page); ++ ++ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->data_devices)); ++ g_list_model_items_changed (G_LIST_MODEL (self->data_devices), 0, n_items, n_items); ++ gtk_container_foreach (GTK_CONTAINER (self->devices_stack), ++ (GtkCallback)cc_wwan_panel_update_page_title, ++ self); ++} ++ ++static void ++cc_wwan_panel_update_data_connections (CcWwanPanel *self) ++{ ++ CcWwanData *device_data, *active_data = NULL; ++ guint n_items; ++ gint i; ++ ++ /* ++ * We can’t predict the order in which the data of device is enabled. ++ * But we have to keep data store in the same order as device store. ++ * So let’s remove every data device and re-add. ++ */ ++ g_list_store_remove_all (self->data_devices); ++ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->devices)); ++ ++ for (i = 0; i < n_items; i++) ++ { ++ g_autoptr(CcWwanDevice) device = NULL; ++ ++ device = g_list_model_get_item (G_LIST_MODEL (self->devices), i); ++ device_data = cc_wwan_device_get_data (device); ++ ++ if (!device_data) ++ continue; ++ ++ if ((!active_data || ++ cc_wwan_data_get_priority (device_data) > cc_wwan_data_get_priority (active_data)) && ++ cc_wwan_data_get_enabled (device_data)) ++ { ++ active_data = device_data; ++ self->data_device = device; ++ } ++ ++ if (cc_wwan_data_get_enabled (device_data)) ++ g_list_store_append (self->data_devices, device); ++ } ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->data_sim_select_listbox), ++ g_list_model_get_n_items (G_LIST_MODEL (self->data_devices)) > 1); ++ if (active_data) ++ gtk_container_foreach (GTK_CONTAINER (self->data_select_listbox), ++ (GtkCallback)cc_wwan_panel_update_data_selection, self); ++ else ++ gtk_label_set_label (self->data_sim_label, ""); ++} ++ ++static void ++cc_wwan_panel_update_devices (CcWwanPanel *self) ++{ ++ GList *devices, *iter; ++ ++ devices = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->mm_manager)); ++ ++ for (iter = devices; iter; iter = iter->next) ++ { ++ MMObject *mm_object = iter->data; ++ CcWwanDevice *device; ++ ++ if(!wwan_panel_device_is_supported (iter->data)) ++ continue; ++ ++ device = cc_wwan_device_new (mm_object, G_OBJECT (self->nm_client)); ++ cc_wwan_panel_add_device (self, device); ++ g_signal_connect_object (device, "notify::has-data", ++ G_CALLBACK (cc_wwan_panel_update_data_connections), ++ self, G_CONNECT_SWAPPED); ++ ++ if (cc_wwan_device_get_data (device)) ++ g_list_store_append (self->data_devices, device); ++ } ++ ++ cc_wwan_panel_update_data_connections (self); ++ handle_argv (self); ++} ++ ++static void ++wwan_panel_device_added_cb (CcWwanPanel *self, ++ GDBusObject *object) ++{ ++ CcWwanDevice *device; ++ ++ if(!wwan_panel_device_is_supported (object)) ++ return; ++ ++ device = cc_wwan_device_new (MM_OBJECT (object), G_OBJECT (self->nm_client)); ++ cc_wwan_panel_add_device (self, device); ++ g_signal_connect_object (device, "notify::has-data", ++ G_CALLBACK (cc_wwan_panel_update_data_connections), ++ self, G_CONNECT_SWAPPED); ++ cc_wwan_panel_update_view (self); ++ handle_argv (self); ++} ++ ++static void ++wwan_panel_device_removed_cb (CcWwanPanel *self, ++ GDBusObject *object) ++{ ++ if (!wwan_panel_device_is_supported (object)) ++ return; ++ ++ cc_wwan_panel_remove_mm_object (self, MM_OBJECT (object)); ++ ++ gtk_revealer_set_reveal_child (self->multi_device_revealer, ++ g_list_model_get_n_items (G_LIST_MODEL (self->devices)) > 1); ++} ++ ++static GPtrArray * ++variant_av_to_string_array (GVariant *array) ++{ ++ GVariant *v; ++ GPtrArray *strv; ++ GVariantIter iter; ++ gsize count; ++ ++ count = g_variant_iter_init (&iter, array); ++ strv = g_ptr_array_sized_new (count + 1); ++ ++ while (g_variant_iter_next (&iter, "v", &v)) ++ { ++ g_ptr_array_add (strv, (gpointer)g_variant_get_string (v, NULL)); ++ g_variant_unref (v); ++ } ++ g_ptr_array_add (strv, NULL); /* NULL-terminate the strv data array */ ++ ++ return strv; ++} ++ ++static void ++cc_wwan_panel_set_property (GObject *object, ++ guint property_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ CcWwanPanel *self = CC_WWAN_PANEL (object); ++ ++ switch (property_id) ++ { ++ case PROP_PARAMETERS: ++ { ++ GVariant *parameters; ++ ++ reset_command_line_args (self); ++ ++ parameters = g_value_get_variant (value); ++ if (parameters) ++ { ++ g_autoptr(GPtrArray) array = NULL; ++ const gchar **args; ++ ++ array = variant_av_to_string_array (parameters); ++ args = (const gchar **) array->pdata; ++ ++ g_debug ("Invoked with operation %s", args[0]); ++ ++ if (args[0]) ++ self->arg_operation = cmdline_operation_from_string (args[0]); ++ if (args[0] && args[1]) ++ self->arg_device = g_strdup (args[1]); ++ ++ if (!verify_argv (self, (const char **) args)) ++ { ++ reset_command_line_args (self); ++ return; ++ } ++ g_debug ("Calling handle_argv() after setting property"); ++ handle_argv (self); ++ } ++ break; ++ } ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); ++ } ++} ++ ++static void ++cc_wwan_panel_constructed (GObject *object) ++{ ++ CcWwanPanel *self = (CcWwanPanel *)object; ++ ++ G_OBJECT_CLASS (cc_wwan_panel_parent_class)->constructed (object); ++ ++ cc_shell_embed_widget_in_header (cc_panel_get_shell (CC_PANEL (self)), ++ GTK_WIDGET (self->enable_switch), GTK_POS_RIGHT); ++ ++ if (self->nm_client) ++ { ++ g_object_bind_property (self->nm_client, "wwan-enabled", ++ self->enable_switch, "active", ++ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); ++ } ++} ++ ++static void ++cc_wwan_panel_dispose (GObject *object) ++{ ++ CcWwanPanel *self = (CcWwanPanel *)object; ++ ++ if (self->revealer_timeout_id != 0) ++ g_source_remove (self->revealer_timeout_id); ++ ++ self->revealer_timeout_id = 0; ++ ++ g_cancellable_cancel (self->cancellable); ++ ++ g_clear_object (&self->devices); ++ g_clear_object (&self->data_devices); ++ g_clear_object (&self->mm_manager); ++ g_clear_object (&self->nm_client); ++ g_clear_object (&self->cancellable); ++ g_clear_object (&self->rfkill_proxy); ++ g_clear_pointer (&self->arg_device, g_free); ++ ++ G_OBJECT_CLASS (cc_wwan_panel_parent_class)->dispose (object); ++} ++ ++static void ++cc_wwan_panel_class_init (CcWwanPanelClass *klass) ++{ ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->set_property = cc_wwan_panel_set_property; ++ object_class->constructed = cc_wwan_panel_constructed; ++ object_class->dispose = cc_wwan_panel_dispose; ++ ++ g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters"); ++ ++ gtk_widget_class_set_template_from_resource (widget_class, ++ "/org/gnome/control-center/wwan/cc-wwan-panel.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_select_listbox); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_select_popover); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_sim_label); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_sim_select_listbox); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, devices_stack); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, devices_switcher); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, enable_switch); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, main_stack); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, multi_device_revealer); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, notification_label); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, notification_revealer); ++ ++ gtk_widget_class_bind_template_callback (widget_class, wwan_on_airplane_off_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, wwan_notification_close_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, wwan_data_selector_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_data_item_activate_cb); ++} ++ ++static void ++cc_wwan_panel_init (CcWwanPanel *self) ++{ ++ g_autoptr(GError) error = NULL; ++ ++ g_resources_register (cc_wwan_get_resource ()); ++ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++ ++ self->cancellable = g_cancellable_new (); ++ self->devices = g_list_store_new (CC_TYPE_WWAN_DEVICE); ++ self->data_devices = g_list_store_new (CC_TYPE_WWAN_DEVICE); ++ gtk_list_box_bind_model (GTK_LIST_BOX (self->data_select_listbox), ++ G_LIST_MODEL (self->data_devices), ++ (GtkListBoxCreateWidgetFunc) cc_data_device_row_new, ++ self, NULL); ++ ++ g_signal_connect_object (self->notification_label, "notify::label", ++ G_CALLBACK (cc_wwan_panel_notification_changed_cb), ++ self, G_CONNECT_SWAPPED); ++ ++ if (cc_object_storage_has_object (CC_OBJECT_NMCLIENT)) ++ { ++ self->nm_client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT); ++ g_signal_connect_object (self->nm_client, ++ "notify::wwan-enabled", ++ G_CALLBACK (cc_wwan_panel_update_view), ++ self, G_CONNECT_SWAPPED); ++ ++ } ++ else ++ { ++ g_warn_if_reached (); ++ } ++ ++ if (cc_object_storage_has_object ("CcObjectStorage::mm-manager")) ++ { ++ self->mm_manager = cc_object_storage_get_object ("CcObjectStorage::mm-manager"); ++ ++ g_signal_connect_object (self->mm_manager, "object-added", ++ G_CALLBACK (wwan_panel_device_added_cb), ++ self, G_CONNECT_SWAPPED); ++ g_signal_connect_object (self->mm_manager, "object-removed", ++ G_CALLBACK (wwan_panel_device_removed_cb), ++ self, G_CONNECT_SWAPPED); ++ ++ cc_wwan_panel_update_devices (self); ++ } ++ else ++ { ++ g_warn_if_reached (); ++ } ++ ++ /* Acquire Airplane Mode proxy */ ++ self->rfkill_proxy = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SESSION, ++ G_DBUS_PROXY_FLAGS_NONE, ++ "org.gnome.SettingsDaemon.Rfkill", ++ "/org/gnome/SettingsDaemon/Rfkill", ++ "org.gnome.SettingsDaemon.Rfkill", ++ self->cancellable, ++ &error); ++ ++ if (error) ++ { ++ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ g_printerr ("Error creating rfkill proxy: %s\n", error->message); ++ } ++ else ++ { ++ g_signal_connect_object (self->rfkill_proxy, ++ "g-properties-changed", ++ G_CALLBACK (cc_wwan_panel_update_view), ++ self, G_CONNECT_SWAPPED); ++ ++ cc_wwan_panel_update_view (self); ++ } ++} ++ ++static void ++wwan_update_panel_visibility (MMManager *mm_manager) ++{ ++ CcApplication *application; ++ GList *devices; ++ gboolean has_wwan; ++ ++ g_assert (MM_IS_MANAGER (mm_manager)); ++ ++ CC_TRACE_MSG ("Updating WWAN panel visibility"); ++ ++ has_wwan = FALSE; ++ devices = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (mm_manager)); ++ ++ for (GList *item = devices; item != NULL; item = item->next) ++ { ++ if(wwan_panel_device_is_supported (item->data)) ++ { ++ has_wwan = TRUE; ++ break; ++ } ++ } ++ ++ /* Set the new visibility */ ++ application = CC_APPLICATION (g_application_get_default ()); ++ cc_shell_model_set_panel_visibility (cc_application_get_model (application), ++ "wwan", ++ has_wwan ? CC_PANEL_VISIBLE : CC_PANEL_VISIBLE_IN_SEARCH); ++ ++ g_debug ("WWAN panel visible: %s", has_wwan ? "yes" : "no"); ++ ++ g_list_free_full (devices, (GDestroyNotify)g_object_unref); ++} ++ ++void ++cc_wwan_panel_static_init_func (void) ++{ ++ g_autoptr(GDBusConnection) system_bus = NULL; ++ g_autoptr(MMManager) mm_manager = NULL; ++ g_autoptr(GError) error = NULL; ++ ++ /* ++ * There could be other modems that are only handled by rfkill, ++ * and not available via ModemManager. But as this panel ++ * makes use of ModemManager APIs, we only care devices ++ * supported by ModemManager. ++ */ ++ system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); ++ if (system_bus == NULL) ++ g_warning ("Error connecting to system D-Bus: %s", error->message); ++ else ++ mm_manager = mm_manager_new_sync (system_bus, ++ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, ++ NULL, &error); ++ ++ if (mm_manager == NULL) ++ { ++ CcApplication *application; ++ ++ g_warning ("Error connecting to ModemManager: %s", error->message); ++ ++ application = CC_APPLICATION (g_application_get_default ()); ++ cc_shell_model_set_panel_visibility (cc_application_get_model (application), ++ "wwan", FALSE); ++ return; ++ } ++ else ++ { ++ cc_object_storage_add_object ("CcObjectStorage::mm-manager", mm_manager); ++ } ++ ++ g_debug ("Monitoring ModemManager for WWAN devices"); ++ ++ g_signal_connect (mm_manager, "object-added", G_CALLBACK (wwan_update_panel_visibility), NULL); ++ g_signal_connect (mm_manager, "object-removed", G_CALLBACK (wwan_update_panel_visibility), NULL); ++ ++ wwan_update_panel_visibility (mm_manager); ++} +diff --git a/panels/wwan/cc-wwan-panel.h b/panels/wwan/cc-wwan-panel.h +new file mode 100644 +index 000000000..57d2dae26 +--- /dev/null ++++ b/panels/wwan/cc-wwan-panel.h +@@ -0,0 +1,36 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-panel.h ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++ ++G_BEGIN_DECLS ++ ++#define CC_TYPE_WWAN_PANEL (cc_wwan_panel_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanPanel, cc_wwan_panel, CC, WWAN_PANEL, CcPanel) ++ ++void cc_wwan_panel_static_init_func (void); ++ ++G_END_DECLS +diff --git a/panels/wwan/cc-wwan-panel.ui b/panels/wwan/cc-wwan-panel.ui +new file mode 100644 +index 000000000..5258c42c3 +--- /dev/null ++++ b/panels/wwan/cc-wwan-panel.ui +@@ -0,0 +1,336 @@ ++ ++ ++ ++ ++ ++ bottom ++ popover_arrow ++ ++ ++ 1 ++ none ++ ++ ++ ++ ++ ++ ++ ++ 1 ++ ++ ++ Enable Mobile Network ++ ++ ++ ++ +diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.c b/panels/wwan/cc-wwan-sim-lock-dialog.c +new file mode 100644 +index 000000000..14adbf415 +--- /dev/null ++++ b/panels/wwan/cc-wwan-sim-lock-dialog.c +@@ -0,0 +1,310 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-network-dialog.c ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#undef G_LOG_DOMAIN ++#define G_LOG_DOMAIN "cc-wwan-sim-lock-dialog" ++ ++#include ++#include ++#include ++ ++#include "list-box-helper.h" ++#include "cc-list-row.h" ++#include "cc-wwan-sim-lock-dialog.h" ++#include "cc-wwan-resources.h" ++ ++/** ++ * @short_description: Dialog to manage SIM Locks like PIN ++ */ ++ ++#define PIN_MINIMUM_LENGTH 4 ++#define PIN_MAXIMUM_LENGTH 8 ++ ++struct _CcWwanSimLockDialog ++{ ++ GtkDialog parent_instance; ++ ++ CcWwanDevice *device; ++ ++ GtkButton *apply_button; ++ GtkStack *button_stack; ++ GtkGrid *lock_change_grid; ++ CcListRow *lock_row; ++ GtkEntry *new_pin_entry; ++ GtkButton *next_button; ++ GtkEntry *pin_confirm_entry; ++ GtkEntry *pin_entry; ++ GtkStack *pin_settings_stack; ++}; ++ ++G_DEFINE_TYPE (CcWwanSimLockDialog, cc_wwan_sim_lock_dialog, GTK_TYPE_DIALOG) ++ ++ ++enum { ++ PROP_0, ++ PROP_DEVICE, ++ N_PROPS ++}; ++ ++static GParamSpec *properties[N_PROPS]; ++ ++static void ++cc_wwan_sim_lock_changed_cb (CcWwanSimLockDialog *self) ++{ ++ gboolean row_enabled, lock_enabled; ++ ++ lock_enabled = cc_wwan_device_get_sim_lock (self->device); ++ row_enabled = cc_list_row_get_active (self->lock_row); ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), lock_enabled != row_enabled); ++ gtk_widget_set_visible (GTK_WIDGET (self->lock_change_grid), row_enabled && lock_enabled); ++} ++ ++static void ++cc_wwan_pin_next_clicked_cb (CcWwanSimLockDialog *self) ++{ ++ gtk_stack_set_visible_child_name (self->pin_settings_stack, "pin-entry"); ++ gtk_entry_set_text (self->pin_entry, ""); ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE); ++ gtk_stack_set_visible_child (self->button_stack, ++ GTK_WIDGET (self->apply_button)); ++} ++ ++static void ++cc_wwan_pin_apply_clicked_cb (CcWwanSimLockDialog *self) ++{ ++ const gchar *pin, *new_pin; ++ gboolean row_enabled, lock_enabled; ++ ++ gtk_widget_hide (GTK_WIDGET (self)); ++ ++ lock_enabled = cc_wwan_device_get_sim_lock (self->device); ++ row_enabled = cc_list_row_get_active (self->lock_row); ++ pin = gtk_entry_get_text (self->pin_entry); ++ new_pin = gtk_entry_get_text (self->new_pin_entry); ++ ++ if (lock_enabled != row_enabled) ++ { ++ if (row_enabled) ++ cc_wwan_device_enable_pin (self->device, pin, NULL, NULL, NULL); ++ else ++ cc_wwan_device_disable_pin (self->device, pin, NULL, NULL, NULL); ++ ++ return; ++ } ++ ++ cc_wwan_device_change_pin (self->device, pin, new_pin, NULL, NULL, NULL); ++} ++ ++static void ++cc_wwan_pin_entry_text_inserted_cb (CcWwanSimLockDialog *self, ++ gchar *new_text, ++ gint new_text_length, ++ gpointer position, ++ GtkEditable *editable) ++{ ++ size_t digit_end; ++ size_t len; ++ ++ if (!new_text || !*new_text) ++ return; ++ ++ if (new_text_length == 1 && g_ascii_isdigit (*new_text)) ++ return; ++ ++ if (new_text_length == -1) ++ len = strlen (new_text); ++ else ++ len = new_text_length; ++ ++ if (len == 1 && g_ascii_isdigit (*new_text)) ++ return; ++ ++ digit_end = strspn (new_text, "1234567890"); ++ ++ /* The maximum length possible for PIN is 8 */ ++ if (len <= 8 && digit_end == len) ++ return; ++ ++ g_signal_stop_emission_by_name (editable, "insert-text"); ++ gtk_widget_error_bell (GTK_WIDGET (editable)); ++} ++ ++static void ++cc_wwan_pin_entry_changed_cb (CcWwanSimLockDialog *self) ++{ ++ const gchar *new_pin, *confirm_pin; ++ ++ new_pin = gtk_entry_get_text (self->new_pin_entry); ++ confirm_pin = gtk_entry_get_text (self->pin_confirm_entry); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), FALSE); ++ ++ /* A PIN should have a minimum length of 4 */ ++ if (!new_pin || !confirm_pin || strlen (new_pin) < 4) ++ return; ++ ++ if (g_str_equal (new_pin, confirm_pin)) ++ gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), TRUE); ++} ++ ++ ++static void ++cc_wwan_pin_entered_cb (CcWwanSimLockDialog *self) ++{ ++ const gchar *pin; ++ gsize len; ++ gboolean enable_apply; ++ ++ pin = gtk_entry_get_text (self->pin_entry); ++ ++ if (!pin || !*pin) ++ { ++ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE); ++ return; ++ } ++ ++ len = strlen (pin); ++ enable_apply = len >= PIN_MINIMUM_LENGTH && len <= PIN_MAXIMUM_LENGTH; ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), enable_apply); ++} ++ ++static void ++cc_wwan_sim_lock_dialog_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ CcWwanSimLockDialog *self = (CcWwanSimLockDialog *)object; ++ ++ switch (prop_id) ++ { ++ case PROP_DEVICE: ++ self->device = g_value_dup_object (value); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++cc_wwan_sim_lock_dialog_show (GtkWidget *widget) ++{ ++ CcWwanSimLockDialog *self = (CcWwanSimLockDialog *)widget; ++ gboolean lock_enabled; ++ ++ gtk_entry_set_text (self->pin_entry, ""); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), FALSE); ++ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE); ++ ++ lock_enabled = cc_wwan_device_get_sim_lock (self->device); ++ g_object_set (self->lock_row, "active", lock_enabled, NULL); ++ gtk_widget_set_visible (GTK_WIDGET (self->lock_change_grid), lock_enabled); ++ ++ gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), FALSE); ++ gtk_stack_set_visible_child (self->button_stack, ++ GTK_WIDGET (self->next_button)); ++ gtk_button_set_label (self->apply_button, _("_Set")); ++ ++ gtk_stack_set_visible_child_name (self->pin_settings_stack, "pin-settings"); ++ ++ gtk_entry_set_text (self->pin_entry, ""); ++ gtk_entry_set_text (self->new_pin_entry, ""); ++ gtk_entry_set_text (self->pin_confirm_entry, ""); ++ ++ GTK_WIDGET_CLASS (cc_wwan_sim_lock_dialog_parent_class)->show (widget); ++} ++ ++static void ++cc_wwan_sim_lock_dialog_dispose (GObject *object) ++{ ++ CcWwanSimLockDialog *self = (CcWwanSimLockDialog *)object; ++ ++ g_clear_object (&self->device); ++ ++ G_OBJECT_CLASS (cc_wwan_sim_lock_dialog_parent_class)->dispose (object); ++} ++ ++static void ++cc_wwan_sim_lock_dialog_class_init (CcWwanSimLockDialogClass *klass) ++{ ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->set_property = cc_wwan_sim_lock_dialog_set_property; ++ object_class->dispose = cc_wwan_sim_lock_dialog_dispose; ++ ++ widget_class->show = cc_wwan_sim_lock_dialog_show; ++ ++ properties[PROP_DEVICE] = ++ g_param_spec_object ("device", ++ "Device", ++ "The WWAN Device", ++ CC_TYPE_WWAN_DEVICE, ++ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++ ++ gtk_widget_class_set_template_from_resource (widget_class, ++ "/org/gnome/control-center/wwan/cc-wwan-sim-lock-dialog.ui"); ++ ++ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, apply_button); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, button_stack); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, lock_change_grid); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, lock_row); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, new_pin_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, next_button); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, pin_confirm_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, pin_entry); ++ gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, pin_settings_stack); ++ ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_sim_lock_changed_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_next_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_apply_clicked_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_entry_text_inserted_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_entry_changed_cb); ++ gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_entered_cb); ++} ++ ++static void ++cc_wwan_sim_lock_dialog_init (CcWwanSimLockDialog *self) ++{ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++} ++ ++CcWwanSimLockDialog * ++cc_wwan_sim_lock_dialog_new (GtkWindow *parent_window, ++ CcWwanDevice *device) ++{ ++ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL); ++ g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL); ++ ++ return g_object_new (CC_TYPE_WWAN_SIM_LOCK_DIALOG, ++ "transient-for", parent_window, ++ "use-header-bar", 1, ++ "device", device, ++ NULL); ++} +diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.h b/panels/wwan/cc-wwan-sim-lock-dialog.h +new file mode 100644 +index 000000000..b6d1d5a9e +--- /dev/null ++++ b/panels/wwan/cc-wwan-sim-lock-dialog.h +@@ -0,0 +1,40 @@ ++/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* cc-wwan-sim-lock-dialog.h ++ * ++ * Copyright 2019 Purism SPC ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * Author(s): ++ * Mohammed Sadiq ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#include "cc-wwan-device.h" ++ ++G_BEGIN_DECLS ++ ++#define CC_TYPE_WWAN_SIM_LOCK_DIALOG (cc_wwan_sim_lock_dialog_get_type()) ++G_DECLARE_FINAL_TYPE (CcWwanSimLockDialog, cc_wwan_sim_lock_dialog, CC, WWAN_SIM_LOCK_DIALOG, GtkDialog) ++ ++CcWwanSimLockDialog *cc_wwan_sim_lock_dialog_new (GtkWindow *parent_window, ++ CcWwanDevice *device); ++ ++G_END_DECLS +diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.ui b/panels/wwan/cc-wwan-sim-lock-dialog.ui +new file mode 100644 +index 000000000..48a946be4 +--- /dev/null ++++ b/panels/wwan/cc-wwan-sim-lock-dialog.ui +@@ -0,0 +1,306 @@ ++ ++ ++ ++ +diff --git a/panels/wwan/gnome-wwan-panel.desktop.in.in b/panels/wwan/gnome-wwan-panel.desktop.in.in +new file mode 100644 +index 000000000..351a8edde +--- /dev/null ++++ b/panels/wwan/gnome-wwan-panel.desktop.in.in +@@ -0,0 +1,16 @@ ++[Desktop Entry] ++Name=Mobile Network ++Comment=Configure Telephony and mobile data connections ++Exec=gnome-control-center wwan ++# FIXME ++# Translators: Do NOT translate or transliterate this text (this is an icon file name)! ++Icon=network-cellular-signal-excellent ++Terminal=false ++Type=Application ++NoDisplay=true ++StartupNotify=true ++Categories=GNOME;GTK;Settings;X-GNOME-NetworkSettings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings; ++OnlyShowIn=GNOME;Unity; ++StartupNotify=true ++# Translators: Search terms to find the WWAN panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! ++Keywords=cellular;wwan;telephony;sim;mobile; +diff --git a/panels/wwan/meson.build b/panels/wwan/meson.build +new file mode 100644 +index 000000000..8c1b02f26 +--- /dev/null ++++ b/panels/wwan/meson.build +@@ -0,0 +1,61 @@ ++gcr_dep = [dependency('gcr-3')] ++ ++deps = common_deps + network_manager_deps + gcr_dep + [polkit_gobject_dep] ++panels_list += cappletname ++desktop = 'gnome-@0@-panel.desktop'.format(cappletname) ++ ++desktop_in = configure_file( ++ input : desktop + '.in.in', ++ output : desktop + '.in', ++ configuration : desktop_conf ++) ++ ++i18n.merge_file( ++ desktop, ++ type : 'desktop', ++ input : desktop_in, ++ output : desktop, ++ po_dir : po_dir, ++ install : true, ++ install_dir : control_center_desktopdir ++) ++ ++sources = files( ++ 'cc-wwan-panel.c', ++ 'cc-wwan-device.c', ++ 'cc-wwan-data.c', ++ 'cc-wwan-device-page.c', ++ 'cc-wwan-mode-dialog.c', ++ 'cc-wwan-network-dialog.c', ++ 'cc-wwan-details-dialog.c', ++ 'cc-wwan-sim-lock-dialog.c', ++ 'cc-wwan-apn-dialog.c', ++) ++ ++resource_data = files( ++ 'cc-wwan-panel.ui', ++ 'cc-wwan-device-page.ui', ++ 'cc-wwan-mode-dialog.ui', ++ 'cc-wwan-network-dialog.ui', ++ 'cc-wwan-details-dialog.ui', ++ 'cc-wwan-sim-lock-dialog.ui', ++ 'cc-wwan-apn-dialog.ui', ++) ++ ++sources += gnome.compile_resources( ++ 'cc-' + cappletname + '-resources', ++ cappletname + '.gresource.xml', ++ c_name : 'cc_' + cappletname, ++ dependencies : resource_data, ++ export : true ++) ++ ++cflags += '-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir) ++ ++panels_libs += static_library( ++ cappletname, ++ sources : sources, ++ include_directories : [ top_inc, common_inc ], ++ dependencies : deps, ++ c_args : cflags ++) +diff --git a/panels/wwan/wwan.gresource.xml b/panels/wwan/wwan.gresource.xml +new file mode 100644 +index 000000000..f128a164a +--- /dev/null ++++ b/panels/wwan/wwan.gresource.xml +@@ -0,0 +1,12 @@ ++ ++ ++ ++ cc-wwan-panel.ui ++ cc-wwan-device-page.ui ++ cc-wwan-mode-dialog.ui ++ cc-wwan-network-dialog.ui ++ cc-wwan-details-dialog.ui ++ cc-wwan-sim-lock-dialog.ui ++ cc-wwan-apn-dialog.ui ++ ++ +diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c +index e23da0b87..e6659b789 100644 +--- a/shell/cc-panel-list.c ++++ b/shell/cc-panel-list.c +@@ -381,6 +381,7 @@ static const gchar * const panel_order[] = { + /* Main page */ + "wifi", + "network", ++ "wwan", + "mobile-broadband", + "bluetooth", + "background", +diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c +index f20384394..65b6555a1 100644 +--- a/shell/cc-panel-loader.c ++++ b/shell/cc-panel-loader.c +@@ -64,6 +64,9 @@ extern GType cc_user_panel_get_type (void); + #ifdef BUILD_WACOM + extern GType cc_wacom_panel_get_type (void); + #endif /* BUILD_WACOM */ ++#ifdef BUILD_WWAN ++extern GType cc_wwan_panel_get_type (void); ++#endif /* BUILD_WWAN */ + extern GType cc_location_panel_get_type (void); + extern GType cc_camera_panel_get_type (void); + extern GType cc_microphone_panel_get_type (void); +@@ -79,6 +82,9 @@ extern void cc_wifi_panel_static_init_func (void); + #ifdef BUILD_WACOM + extern void cc_wacom_panel_static_init_func (void); + #endif /* BUILD_WACOM */ ++#ifdef BUILD_WWAN ++extern void cc_wwan_panel_static_init_func (void); ++#endif /* BUILD_WWAN */ + + #define PANEL_TYPE(name, get_type, init_func) { name, get_type, init_func } + +@@ -129,6 +135,9 @@ static CcPanelLoaderVtable default_panels[] = + #ifdef BUILD_WACOM + PANEL_TYPE("wacom", cc_wacom_panel_get_type, cc_wacom_panel_static_init_func), + #endif ++#ifdef BUILD_WWAN ++ PANEL_TYPE("wwan", cc_wwan_panel_get_type, cc_wwan_panel_static_init_func), ++#endif + }; + + /* Override for the panel vtable. When NULL, the default_panels will +-- +2.32.0 + + +From 610bf914b5c745c87b0be5c827515e82c07317f9 Mon Sep 17 00:00:00 2001 +From: Mohammed Sadiq +Date: Fri, 4 Oct 2019 16:02:23 +0530 +Subject: [PATCH 3/7] network: Don't show modems supported by cellular panel + +Cellular panel is already handling it +--- + panels/network/cc-network-panel.c | 25 +++++++++++++++++++++++++ + 1 file changed, 25 insertions(+) + +diff --git a/panels/network/cc-network-panel.c b/panels/network/cc-network-panel.c +index 01b164ea0..bd4e55df8 100644 +--- a/panels/network/cc-network-panel.c ++++ b/panels/network/cc-network-panel.c +@@ -382,6 +382,27 @@ update_bluetooth_section (CcNetworkPanel *self) + gtk_widget_set_visible (self->container_bluetooth, self->bluetooth_devices->len > 0); + } + ++static gboolean ++wwan_panel_supports_modem (GDBusObject *object) ++{ ++ MMObject *mm_object; ++ MMModem *modem; ++ MMModemCapability capability, supported_capabilities; ++ ++ g_assert (G_IS_DBUS_OBJECT (object)); ++ ++ supported_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE; ++#if MM_CHECK_VERSION (1,14,0) ++ supported_capabilities |= MM_MODEM_CAPABILITY_5GNR; ++#endif ++ ++ mm_object = MM_OBJECT (object); ++ modem = mm_object_get_modem (mm_object); ++ capability = mm_modem_get_current_capabilities (modem); ++ ++ return capability & supported_capabilities; ++} ++ + static void + panel_add_device (CcNetworkPanel *self, NMDevice *device) + { +@@ -425,6 +446,10 @@ panel_add_device (CcNetworkPanel *self, NMDevice *device) + nm_device_get_udi (device)); + return; + } ++ ++ /* This will be handled by cellular panel */ ++ if (wwan_panel_supports_modem (modem_object)) ++ return; + } + + device_mobile = net_device_mobile_new (self->client, device, modem_object); +-- +2.32.0 + + +From b7a3b8b84641fdc600cc2ab3683da57023ef5e65 Mon Sep 17 00:00:00 2001 +From: Kyle Rankin +Date: Fri, 21 Feb 2020 01:28:13 +0000 +Subject: [PATCH 4/7] Lower WWAN DNS Priority + +The current DNS priority settings for WWAN were set far too low. Most +connections (including WiFi) do not set DNS priority (set to 0) and per +https://developer.gnome.org/NetworkManager/stable/nm-settings.html : + +"A lower value is better (higher priority). Zero selects a globally +configured default value. If the latter is missing or zero too, it +defaults to 50 for VPNs and 100 for other connections." + +By setting both the "low" and "high" settings to 15 and 20 respectively, +the WWAN DNS servers were always appearing above WiFi, even though WiFi +had routing priority. This caused latency and other problems when the +wwan connection was slow because the system would query those DNS +servers before WiFi ones. Beyond that, it would even cause WWAN to +override VPN DNS settings which isn't what we want. + +This change puts the "low priority" setting above the default 100 that +connections get when they don't otherwise set a priority, and the "high +priority" slightly below 100. I did this instead of setting the values +to 0 because I noticed that NM doesn't seem to be aware it should +prioritize WiFi in that case so WWAN DNS servers were still sometimes +taking precedence. +--- + panels/wwan/cc-wwan-data.c | 17 ++++++++++++++--- + 1 file changed, 14 insertions(+), 3 deletions(-) + +diff --git a/panels/wwan/cc-wwan-data.c b/panels/wwan/cc-wwan-data.c +index 0be8f3403..4062d78fd 100644 +--- a/panels/wwan/cc-wwan-data.c ++++ b/panels/wwan/cc-wwan-data.c +@@ -47,9 +47,20 @@ + * of #CcWwanData changes when SIM is changed. + */ + +-/* Priority for connections. larger the number, lower the priority */ +-#define CC_WWAN_DNS_PRIORITY_LOW (20) +-#define CC_WWAN_DNS_PRIORITY_HIGH (15) ++/* ++ * Priority for connections. The larger the number, the lower the priority ++ * https://developer.gnome.org/NetworkManager/stable/nm-settings.html: ++ * ++ * A lower value is better (higher priority). Zero selects a globally ++ * configured default value. If the latter is missing or zero too, it ++ * defaults to 50 for VPNs and 100 for other connections. ++ * ++ * Since WiFi and other network connections will likely get the default ++ * setting of 100, set WWAN DNS priorities higher than the default, with ++ * room to allow multiple modems to set priority above/below each other. ++ */ ++#define CC_WWAN_DNS_PRIORITY_LOW (120) ++#define CC_WWAN_DNS_PRIORITY_HIGH (115) + + /* These are to be set as route metric */ + #define CC_WWAN_ROUTE_PRIORITY_LOW (1050) +-- +2.32.0 + + +From 0f38f32b98ae946b8259e3232e311d4f743acaba Mon Sep 17 00:00:00 2001 +From: Sebastian Krzyszkowiak +Date: Mon, 6 Jul 2020 04:33:30 +0200 +Subject: [PATCH 5/7] wwan: Fix signal strength display when extended signal + retrieval is disabled + +MMModemSignal interface is used to retrieve extended signal information that +requires periodic polling. Therefore, it needs to be manually enabled in order +to use. There if a fallback to use mm_modem_get_signal_quality when MMModemSignal +interface is unavailable, but it didn't check whether it's actually enabled, +leaving the UI with empty label. +--- + panels/wwan/cc-wwan-device.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/panels/wwan/cc-wwan-device.c b/panels/wwan/cc-wwan-device.c +index 31baff95c..55a627a5a 100644 +--- a/panels/wwan/cc-wwan-device.c ++++ b/panels/wwan/cc-wwan-device.c +@@ -1183,12 +1183,16 @@ cc_wwan_device_dup_signal_string (CcWwanDevice *self) + GString *str; + gdouble value; + gboolean recent; ++ guint refresh_rate; + + g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL); + + modem_signal = mm_object_peek_modem_signal (self->mm_object); + +- if (!modem_signal) ++ if (modem_signal) ++ refresh_rate = mm_modem_signal_get_rate (modem_signal); ++ ++ if (!modem_signal || !refresh_rate) + return g_strdup_printf ("%d%%", mm_modem_get_signal_quality (self->modem, &recent)); + + str = g_string_new (""); +-- +2.32.0 + + +From 99bbe06a25a523898dde7370274430e7502be53e Mon Sep 17 00:00:00 2001 +From: Mohammed Sadiq +Date: Sat, 14 Aug 2021 13:39:33 +0530 +Subject: [PATCH 6/7] wwan: Fix a typo + +Fixes https://gitlab.gnome.org/GNOME/gnome-control-center/-/commit/dc840f0aec346f3fb297789eb1641255574c47a4#note_1249116 +--- + panels/wwan/cc-wwan-apn-dialog.ui | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/panels/wwan/cc-wwan-apn-dialog.ui b/panels/wwan/cc-wwan-apn-dialog.ui +index fb8432bc6..9ac07ce38 100644 +--- a/panels/wwan/cc-wwan-apn-dialog.ui ++++ b/panels/wwan/cc-wwan-apn-dialog.ui +@@ -211,7 +211,7 @@ + 1 + end + center +- Passsword ++ Password + +-- +2.32.0 + + +From b1cf8916b2569aa6c3b7f724eff37f198ed37b73 Mon Sep 17 00:00:00 2001 +From: Mohammed Sadiq +Date: Sat, 14 Aug 2021 13:54:33 +0530 +Subject: [PATCH 7/7] wwan: Avoid translation of some strings + +Many strings are not shown in the UI. Let's not overwhelm translators +--- + panels/wwan/cc-wwan-errors-private.h | 50 ++++++++++++++-------------- + 1 file changed, 25 insertions(+), 25 deletions(-) + +diff --git a/panels/wwan/cc-wwan-errors-private.h b/panels/wwan/cc-wwan-errors-private.h +index 761b82f35..076482d1f 100644 +--- a/panels/wwan/cc-wwan-errors-private.h ++++ b/panels/wwan/cc-wwan-errors-private.h +@@ -39,12 +39,12 @@ typedef struct { + static ErrorTable me_errors[] = { + { MM_MOBILE_EQUIPMENT_ERROR_PHONE_FAILURE, N_("Phone failure") }, + { MM_MOBILE_EQUIPMENT_ERROR_NO_CONNECTION, N_("No connection to phone") }, +- { MM_MOBILE_EQUIPMENT_ERROR_LINK_RESERVED, N_("Phone-adaptor link reserved") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_LINK_RESERVED, "Phone-adaptor link reserved" }, + { MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED, N_("Operation not allowed") }, + { MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, N_("Operation not supported") }, +- { MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN, N_("PH-SIM PIN required") }, +- { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN, N_("PH-FSIM PIN required") }, +- { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK, N_("PH-FSIM PUK required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN, "PH-SIM PIN required" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN, "PH-FSIM PIN required" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK, "PH-FSIM PUK required" }, + { MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED, N_("SIM not inserted") }, + { MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN, N_("SIM PIN required") }, + { MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK, N_("SIM PUK required") }, +@@ -54,34 +54,34 @@ static ErrorTable me_errors[] = { + { MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD, N_("Incorrect password") }, + { MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN2, N_("SIM PIN2 required") }, + { MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK2, N_("SIM PUK2 required") }, +- { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FULL, N_("Memory full") }, +- { MM_MOBILE_EQUIPMENT_ERROR_INVALID_INDEX, N_("Invalid index") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FULL, "Memory full" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_INVALID_INDEX, "Invalid index" }, + { MM_MOBILE_EQUIPMENT_ERROR_NOT_FOUND, N_("Not found") }, +- { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FAILURE, N_("Memory failure") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FAILURE, "Memory failure" }, + { MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK, N_("No network service") }, + { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, N_("Network timeout") }, +- { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED, N_("Network not allowed - emergency calls only") }, +- { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN, N_("Network personalization PIN required") }, +- { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK, N_("Network personalization PUK required") }, +- { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN, N_("Network subset personalization PIN required") }, +- { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK, N_("Network subset personalization PUK required") }, +- { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN, N_("Service provider personalization PIN required") }, +- { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK, N_("Service provider personalization PUK required") }, +- { MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN, N_("Corporate personalization PIN required") }, +- { MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK, N_("Corporate personalization PUK required") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED, "Network not allowed - emergency calls only" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN, "Network personalization PIN required" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK, "Network personalization PUK required" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN, "Network subset personalization PIN required" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK, "Network subset personalization PUK required" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN, "Service provider personalization PIN required" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK, "Service provider personalization PUK required" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN, "Corporate personalization PIN required" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK, "Corporate personalization PUK required" }, + { MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, N_("Unknown error") }, +- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_MS, N_("Illegal MS") }, +- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_ME, N_("Illegal ME") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_MS, "Illegal MS" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_ME, "Illegal ME" }, + { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_NOT_ALLOWED, N_("GPRS services not allowed") }, +- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PLMN_NOT_ALLOWED, N_("PLMN not allowed") }, +- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_LOCATION_NOT_ALLOWED, N_("Location area not allowed") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PLMN_NOT_ALLOWED, "PLMN not allowed" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_LOCATION_NOT_ALLOWED, "Location area not allowed" }, + { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ROAMING_NOT_ALLOWED, N_("Roaming not allowed in this location area") }, +- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUPPORTED, N_("Service option not supported") }, +- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUBSCRIBED, N_("Requested service option not subscribed") }, +- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_OUT_OF_ORDER, N_("Service option temporarily out of order") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUPPORTED, "Service option not supported" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUBSCRIBED, "Requested service option not subscribed" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_OUT_OF_ORDER, "Service option temporarily out of order" }, + { MM_MOBILE_EQUIPMENT_ERROR_GPRS_UNKNOWN, N_("Unspecified GPRS error") }, +- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PDP_AUTH_FAILURE, N_("PDP authentication failure") }, +- { MM_MOBILE_EQUIPMENT_ERROR_GPRS_INVALID_MOBILE_CLASS, N_("Invalid mobile class") }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PDP_AUTH_FAILURE, "PDP authentication failure" }, ++ { MM_MOBILE_EQUIPMENT_ERROR_GPRS_INVALID_MOBILE_CLASS, "Invalid mobile class" }, + }; + + static inline const gchar * +-- +2.32.0 + diff --git a/SPECS/gnome-control-center.spec b/SPECS/gnome-control-center.spec new file mode 100644 index 0000000..78600bb --- /dev/null +++ b/SPECS/gnome-control-center.spec @@ -0,0 +1,506 @@ +%define gnome_online_accounts_version 3.25.3 +%define glib2_version 2.56.0 +%define gnome_desktop_version 3.35.4 +%define gsd_version 3.35.0 +%define gsettings_desktop_schemas_version 3.37.1 +%define upower_version 0.99.8 +%define gtk3_version 3.22.20 +%define cheese_version 3.28.0 +%define gnome_bluetooth_version 3.18.2 +%define nm_version 1.24 +%define power_profiles_daemon_version 0.9.0 + +%global tarball_version %%(echo %{version} | tr '~' '.') + +Name: gnome-control-center +Version: 40.0 +Release: 16%{?dist}.1 +Summary: Utilities to configure the GNOME desktop + +License: GPLv2+ and CC-BY-SA +URL: http://www.gnome.org +Source0: https://download.gnome.org/sources/gnome-control-center/40/gnome-control-center-%{tarball_version}.tar.xz + +# https://gitlab.gnome.org/GNOME/gnome-control-center/-/merge_requests/965 +Patch0: distro-logo.patch + +# Customized for RHEL 9 to skip the .gitlab-ci.yml file +# https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/1345 +# https://bugzilla.redhat.com/show_bug.cgi?id=1952274 +Patch1: gnome-control-center-Drop-the-unused-build-dependency-on-Grilo.patch +Patch2: power-profiles-backport.patch +Patch3: wwan-backport-gnome-40.patch +Patch4: subscription-manager-support.patch + +BuildRequires: chrpath +BuildRequires: cups-devel +BuildRequires: desktop-file-utils +BuildRequires: docbook-style-xsl libxslt +BuildRequires: gcc +BuildRequires: gettext +BuildRequires: meson +BuildRequires: pkgconfig(accountsservice) +BuildRequires: pkgconfig(cheese) >= %{cheese_version} +BuildRequires: pkgconfig(cheese-gtk) +BuildRequires: pkgconfig(clutter-gtk-1.0) +BuildRequires: pkgconfig(colord) +BuildRequires: pkgconfig(colord-gtk) +BuildRequires: pkgconfig(gdk-pixbuf-2.0) +BuildRequires: pkgconfig(gdk-wayland-3.0) +BuildRequires: pkgconfig(gio-2.0) >= %{glib2_version} +BuildRequires: pkgconfig(gnome-desktop-3.0) >= %{gnome_desktop_version} +BuildRequires: pkgconfig(gnome-settings-daemon) >= %{gsd_version} +BuildRequires: pkgconfig(goa-1.0) >= %{gnome_online_accounts_version} +BuildRequires: pkgconfig(goa-backend-1.0) +BuildRequires: pkgconfig(gsettings-desktop-schemas) >= %{gsettings_desktop_schemas_version} +BuildRequires: pkgconfig(gsound) +BuildRequires: pkgconfig(gtk+-3.0) >= %{gtk3_version} +BuildRequires: pkgconfig(gudev-1.0) +BuildRequires: pkgconfig(ibus-1.0) +BuildRequires: pkgconfig(libcanberra-gtk3) +BuildRequires: pkgconfig(libgtop-2.0) +BuildRequires: pkgconfig(libhandy-1) +BuildRequires: pkgconfig(libnm) >= %{nm_version} +BuildRequires: pkgconfig(libnma) +BuildRequires: pkgconfig(libpulse) +BuildRequires: pkgconfig(libpulse-mainloop-glib) +BuildRequires: pkgconfig(libsecret-1) +BuildRequires: pkgconfig(libsoup-2.4) +BuildRequires: pkgconfig(libxml-2.0) +BuildRequires: pkgconfig(mm-glib) +BuildRequires: pkgconfig(polkit-gobject-1) +BuildRequires: pkgconfig(pwquality) +BuildRequires: pkgconfig(smbclient) +BuildRequires: pkgconfig(upower-glib) >= %{upower_version} +BuildRequires: pkgconfig(x11) +BuildRequires: pkgconfig(xi) +BuildRequires: pkgconfig(udisks2) +BuildRequires: pkgconfig(gcr-3) +%ifnarch s390 s390x +BuildRequires: pkgconfig(gnome-bluetooth-1.0) >= %{gnome_bluetooth_version} +BuildRequires: pkgconfig(libwacom) +%endif + +# Versioned library deps +Requires: cheese-libs%{?_isa} >= %{cheese_version} +Requires: glib2%{?_isa} >= %{glib2_version} +Requires: gnome-desktop3%{?_isa} >= %{gnome_desktop_version} +Requires: gnome-online-accounts%{?_isa} >= %{gnome_online_accounts_version} +Requires: gnome-settings-daemon%{?_isa} >= %{gsd_version} +# For g-s-d subscription manager patches +Requires: gnome-settings-daemon%{?_isa} >= 40.0.1-4 +Requires: gsettings-desktop-schemas%{?_isa} >= %{gsettings_desktop_schemas_version} +Requires: gtk3%{?_isa} >= %{gtk3_version} +Requires: upower%{?_isa} >= %{upower_version} +Requires: power-profiles-daemon >= %{power_profiles_daemon_version} +%ifnarch s390 s390x +Requires: gnome-bluetooth%{?_isa} >= 1:%{gnome_bluetooth_version} +%endif + +Requires: %{name}-filesystem = %{version}-%{release} +# For user accounts +Requires: accountsservice +Requires: alsa-lib +# For the thunderbolt panel +Recommends: bolt +# For the color panel +Requires: colord +# For the printers panel +Requires: cups-pk-helper +Requires: dbus +# For the info/details panel +Requires: glx-utils +# For the user languages +Requires: iso-codes +# For the network panel +Recommends: NetworkManager-wifi +Recommends: nm-connection-editor +# For Show Details in the color panel +Recommends: gnome-color-manager +# For the sharing panel +Recommends: gnome-remote-desktop +%if 0%{?fedora} +Recommends: rygel +%endif +# For the info/details panel +Recommends: switcheroo-control +# For the keyboard panel +Requires: /usr/bin/gkbd-keyboard-display +%if 0%{?fedora} >= 35 || 0%{?rhel} >= 9 +# For the power panel +Recommends: power-profiles-daemon +%endif + +# Renamed in F28 +Provides: control-center = 1:%{version}-%{release} +Provides: control-center%{?_isa} = 1:%{version}-%{release} +Obsoletes: control-center < 1:%{version}-%{release} + +%description +This package contains configuration utilities for the GNOME desktop, which +allow to configure accessibility options, desktop fonts, keyboard and mouse +properties, sound setup, desktop theme and background, user interface +properties, screen resolution, and other settings. + +%package filesystem +Summary: GNOME Control Center directories +# NOTE: this is an "inverse dep" subpackage. It gets pulled in +# NOTE: by the main package and MUST not depend on the main package +BuildArch: noarch +# Renamed in F28 +Provides: control-center-filesystem = 1:%{version}-%{release} +Obsoletes: control-center-filesystem < 1:%{version}-%{release} + +%description filesystem +The GNOME control-center provides a number of extension points +for applications. This package contains directories where applications +can install configuration files that are picked up by the control-center +utilities. + +%prep +%autosetup -p1 -n gnome-control-center-%{tarball_version} + +%build +%meson \ + -Ddocumentation=true \ +%if 0%{?fedora} + -Ddistributor_logo=%{_datadir}/pixmaps/fedora_logo_med.png \ + -Ddark_mode_distributor_logo=%{_datadir}/pixmaps/fedora_whitelogo_med.png \ +%endif +%if 0%{?rhel} + -Ddistributor_logo=%{_datadir}/pixmaps/fedora-logo.png \ + -Ddark_mode_distributor_logo=%{_datadir}/pixmaps/system-logo-white.png \ +%endif + %{nil} +%meson_build + +%install +%meson_install + +# We do want this +mkdir -p $RPM_BUILD_ROOT%{_datadir}/gnome/wm-properties + +# We don't want these +rm -rf $RPM_BUILD_ROOT%{_datadir}/gnome/autostart +rm -rf $RPM_BUILD_ROOT%{_datadir}/gnome/cursor-fonts + +# Remove rpath +chrpath --delete $RPM_BUILD_ROOT%{_bindir}/gnome-control-center + +%find_lang %{name} --all-name --with-gnome + +%files -f %{name}.lang +%license COPYING +%doc NEWS README.md +%{_bindir}/gnome-control-center +%{_datadir}/applications/*.desktop +%{_datadir}/bash-completion/completions/gnome-control-center +%{_datadir}/dbus-1/services/org.gnome.ControlCenter.SearchProvider.service +%{_datadir}/dbus-1/services/org.gnome.ControlCenter.service +%{_datadir}/gettext/ +%{_datadir}/glib-2.0/schemas/org.gnome.ControlCenter.gschema.xml +%{_datadir}/gnome-control-center/keybindings/*.xml +%{_datadir}/gnome-control-center/pixmaps +%{_datadir}/gnome-shell/search-providers/gnome-control-center-search-provider.ini +%{_datadir}/icons/hicolor/*/*/* +%{_datadir}/man/man1/gnome-control-center.1* +%{_datadir}/metainfo/gnome-control-center.appdata.xml +%{_datadir}/pixmaps/faces +%{_datadir}/pkgconfig/gnome-keybindings.pc +%{_datadir}/polkit-1/actions/org.gnome.controlcenter.*.policy +%{_datadir}/polkit-1/rules.d/gnome-control-center.rules +%{_datadir}/sounds/gnome/default/*/*.ogg +%{_libexecdir}/cc-remote-login-helper +%{_libexecdir}/gnome-control-center-search-provider +%{_libexecdir}/gnome-control-center-print-renderer + +%files filesystem +%dir %{_datadir}/gnome-control-center +%dir %{_datadir}/gnome-control-center/keybindings +%dir %{_datadir}/gnome/wm-properties + +%changelog +* Wed Sep 15 2021 Kalev Lember - 40.0-16.1 +- Rebuilt +- Related: #2002166 + +* Tue Sep 07 2021 Kalev Lember - 40.0-16 +- Add desktop file keywords for subscription support +- Resolves: #2002166 + +* Thu Sep 02 2021 Kalev Lember - 40.0-15 +- Forward port subscription manager support from RHEL 8 +- Resolves: #2002166 + +* Wed Aug 25 2021 Carlos Garnacho - 40.0-14 +- Backport WWAN panel + Resolves: #1995559 + +* Fri Aug 20 2021 Carlos Garnacho - 40.0.13 +- Backport power profile changes + Resolves: #1994475 + +* Mon Aug 09 2021 Mohan Boddu - 40.0-12 +- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags + Related: rhbz#1991688 + +* Tue Apr 27 2021 Debarshi Ray - 40.0-11 +- Drop the unused build dependency on Grilo +Resolves: #1952274 + +* Thu Apr 15 2021 Mohan Boddu - 40.0-10 +- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937 + +* Fri Apr 02 2021 Kalev Lember - 40.0-9 +- Only enable power-profiles-daemon on F35+ and RHEL 9+ + +* Wed Mar 31 2021 Pete Walter - 40.0-8 +- Add back power-profiles-daemon once more + +* Wed Mar 31 2021 Michael Catanzaro - 40.0-7 +- Drop Recommends: power-profiles-daemon for F34 + +* Tue Mar 30 2021 Pete Walter - 40.0-6 +- Use recommends for a few more things + +* Tue Mar 30 2021 Bastien Nocera - 40.0-4 +- Drag power-profiles-daemon in for the power panel + +* Mon Mar 29 2021 Michael Catanzaro - 40.0-3 +- Update Fedora logos to larger versions + +* Wed Mar 24 2021 Kalev Lember - 40.0-2 +- Rebuilt + +* Mon Mar 22 2021 Kalev Lember - 40.0-1 +- Update to 40.0 + +* Mon Mar 15 2021 Kalev Lember - 40~rc-1 +- Update to 40.rc + +* Wed Mar 10 2021 Michael Catanzaro - 40~beta-5 +- Refresh distro logo patch +- Drop Recommends: vino, let vino die! + +* Sun Mar 07 2021 Igor Raits - 40~beta-4 +- Fix modifications of the networks (Fixes: RHBZ#1932674) + +* Wed Feb 24 2021 Felipe Borges - 40~beta-3 +- Include missing patch from 40~beta-2 + +* Tue Feb 23 2021 Felipe Borges - 40~beta-2 +- Fix error preventing the Region & Language panel from loading + +* Sun Feb 21 2021 Kalev Lember - 40~beta-1 +- Update to 40.beta + +* Mon Feb 15 2021 Kalev Lember - 3.38.4-1 +- Update to 3.38.4 + +* Tue Jan 26 2021 Fedora Release Engineering - 3.38.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Sat Jan 16 2021 Kalev Lember - 3.38.3-1 +- Update to 3.38.3 + +* Fri Nov 20 2020 Kalev Lember - 3.38.2-2 +- search: Check for either tracker 2.x or 3.x schemas + +* Fri Nov 20 2020 Kalev Lember - 3.38.2-1 +- Update to 3.38.2 + +* Tue Oct 13 2020 Kalev Lember - 3.38.1-2 +- Add Recommends: nm-connection-editor for the network panel (#1887891) + +* Mon Oct 5 2020 Kalev Lember - 3.38.1-1 +- Update to 3.38.1 + +* Sat Sep 19 2020 Yaroslav Fedevych - 3.38.0-2 +- Specify the minimum libnm version needed to build the package + +* Sat Sep 12 2020 Kalev Lember - 3.38.0-1 +- Update to 3.38.0 + +* Sun Sep 06 2020 Kalev Lember - 3.37.92-1 +- Update to 3.37.92 + +* Mon Aug 17 2020 Kalev Lember - 3.37.90-1 +- Update to 3.37.90 + +* Tue Aug 04 2020 Michael Catanzaro - 3.37.3-4 +- Add Recommends: gnome-color-manager for the color panel + +* Sat Aug 01 2020 Fedora Release Engineering - 3.37.3-3 +- Second attempt - Rebuilt for + https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Mon Jul 27 2020 Fedora Release Engineering - 3.37.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Mon Jul 20 2020 Kalev Lember - 3.37.3-1 +- Update to 3.37.3 + +* Mon Jul 20 2020 Kalev Lember - 3.36.4-1 +- Update to 3.36.4 + +* Wed Jun 03 2020 Kalev Lember - 3.36.3-1 +- Update to 3.36.3 + +* Fri May 01 2020 Kalev Lember - 3.36.2-1 +- Update to 3.36.2 + +* Tue Apr 28 2020 Felipe Borges - 3.36.1-2 +- Add "Model" row info for Lenovo devices + +* Fri Mar 27 2020 Kalev Lember - 3.36.1-1 +- Update to 3.36.1 + +* Thu Mar 19 2020 Michael Catanzaro - 3.36.0-3 +- No changes, bump revision to maintain upgrade path from F32 + +* Mon Mar 16 2020 Michael Catanzaro - 3.36.0-2 +- Update distro-logo.patch to use fedora_vertical version of logo. + +* Sat Mar 07 2020 Kalev Lember - 3.36.0-1 +- Update to 3.36.0 + +* Mon Mar 02 2020 Kalev Lember - 3.35.92-1 +- Update to 3.35.92 + +* Mon Feb 17 2020 Kalev Lember - 3.35.91-1 +- Update to 3.35.91 + +* Mon Feb 03 2020 Bastien Nocera - 3.35.90-1 ++ gnome-control-center-3.35.90-1 +- Update to 3.35.90 + +* Tue Jan 28 2020 Fedora Release Engineering - 3.34.2-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Thu Jan 16 2020 Kalev Lember - 3.34.2-3 +- Backport a patch to fix the build with latest libgnome-desktop + +* Mon Dec 09 2019 Michael Catanzaro - 3.34.2-2 +- Drop nm-connection-editor requires, per gnome-control-center#512 +- To edit mobile broadband connections, install nm-connection-editor + +* Wed Nov 27 2019 Kalev Lember - 3.34.2-1 +- Update to 3.34.2 + +* Thu Oct 10 2019 Adam Williamson - 3.34.1-4 +- Add patch to fix crash when selecting display with no modes (rhbz#1756553) + +* Wed Oct 09 2019 Felipe Borges - 3.34.1-3 +- Add patch to fix parsing of addresses while adding printers (rhbz#1750394) + +* Mon Oct 07 2019 Benjamin Berg - 3.34.1-2 +- Add patch to fix resetting of system wide format locale (rhbz#1759221) + +* Mon Oct 07 2019 Kalev Lember - 3.34.1-1 +- Update to 3.34.1 + +* Sat Oct 05 2019 Michael Catanzaro - 3.34.0.1-3 +- Add patch to fix editing wired connection settings (rhbz#1750805) +- Remove broken remote printers patch + +* Wed Oct 02 2019 Michael Catanzaro - 3.34.0.1-2 +- Add patch to fix crash when configuring remote printers + +* Mon Sep 09 2019 Kalev Lember - 3.34.0.1-1 +- Update to 3.34.0.1 + +* Mon Sep 09 2019 Kalev Lember - 3.34.0-1 +- Update to 3.34.0 + +* Mon Aug 12 2019 Kalev Lember - 3.33.90-1 +- Update to 3.33.90 + +* Thu Jul 25 2019 Fedora Release Engineering - 3.33.3-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Sun Jul 21 2019 Kalev Lember - 3.33.3-2 +- Remove libXxf86misc-devel BuildRequires as the package no longer exists + +* Wed Jun 19 2019 Kalev Lember - 3.33.3-1 +- Update to 3.33.3 + +* Fri May 24 2019 Kalev Lember - 3.32.2-1 +- Update to 3.32.2 + +* Tue Apr 16 2019 Adam Williamson - 3.32.1-2 +- Rebuild with Meson fix for #1699099 + +* Fri Mar 29 2019 Kalev Lember - 3.32.1-1 +- Update to 3.32.1 + +* Mon Mar 11 2019 Kalev Lember - 3.32.0.1-1 +- Update to 3.32.0.1 + +* Mon Mar 11 2019 Kalev Lember - 3.32.0-1 +- Update to 3.32.0 + +* Mon Mar 04 2019 Kalev Lember - 3.31.92-1 +- Update to 3.31.92 + +* Sat Feb 23 2019 Kevin Fenzi - 3.31.90-2 +- Add https://gitlab.gnome.org/GNOME/gnome-control-center/merge_requests/387.patch + to fix udisks crash + +* Thu Feb 07 2019 Kalev Lember - 3.31.90-1 +- Update to 3.31.90 + +* Thu Jan 31 2019 Fedora Release Engineering - 3.31.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Wed Jan 09 2019 Kalev Lember - 3.31.4-1 +- Update to 3.31.4 + +* Tue Nov 20 2018 Pete Walter - 3.30.2-3 +- Recommend gnome-remote-desktop for the sharing panel + +* Sat Nov 17 2018 Pete Walter - 3.30.2-2 +- Change bolt requires to recommends (#1643709) +- Change rygel requires to recommends + +* Thu Nov 01 2018 Kalev Lember - 3.30.2-1 +- Update to 3.30.2 + +* Thu Oct 11 2018 David Herrmann - 3.30.1-4 +- Reduce 'dbus-x11' dependency to 'dbus'. The xinit scripts are no longer the + canonical way to start dbus, but the 'dbus' package is nowadays required to + provide a user and system bus to its dependents. + +* Wed Oct 10 2018 Benjamin Berg - 3.30.1-3 +- Add patch to improve background loading. The patch is not acceptable + upstream as is, but is also a good improvement on the current situation + (#1631002) + +* Sun Oct 07 2018 Kalev Lember - 3.30.1-2 +- Backport an upstream fix for a crash in the online accounts panel + +* Wed Sep 26 2018 Kalev Lember - 3.30.1-1 +- Update to 3.30.1 + +* Thu Sep 06 2018 Kalev Lember - 3.30.0-1 +- Update to 3.30.0 + +* Sun Aug 12 2018 Kalev Lember - 3.29.90-1 +- Update to 3.29.90 + +* Fri Jul 13 2018 Fedora Release Engineering - 3.28.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Tue May 29 2018 Kalev Lember - 3.28.2-1 +- Update to 3.28.2 + +* Wed May 23 2018 Pete Walter - 3.28.1-4 +- Change NetworkManager-wifi requires to recommends (#1478661) + +* Tue May 22 2018 Ray Strode - 3.28.1-3 +- Change vino requires to a vino recommends + +* Fri Apr 13 2018 Kalev Lember - 3.28.1-2 +- Backport new thunderbolt panel + +* Tue Apr 10 2018 Pete Walter - 3.28.1-1 +- Rename control-center to gnome-control-center