diff --git a/gnome-control-center.spec b/gnome-control-center.spec index 950ecbd..dea71a7 100644 --- a/gnome-control-center.spec +++ b/gnome-control-center.spec @@ -14,7 +14,7 @@ Name: gnome-control-center Version: 40.0 -Release: 14%{?dist} +Release: 15%{?dist} Summary: Utilities to configure the GNOME desktop License: GPLv2+ and CC-BY-SA @@ -30,6 +30,7 @@ Patch0: distro-logo.patch 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 @@ -86,6 +87,8 @@ 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} @@ -217,6 +220,10 @@ chrpath --delete $RPM_BUILD_ROOT%{_bindir}/gnome-control-center %dir %{_datadir}/gnome/wm-properties %changelog +* Thu Sep 02 2021 Kalev Lember - 40.0-15 +- Forward port subscription manager support from RHEL 8 +- Resolves: #1937113 + * Wed Aug 25 2021 Carlos Garnacho - 40.0-14 - Backport WWAN panel Resolves: #1995559 diff --git a/subscription-manager-support.patch b/subscription-manager-support.patch new file mode 100644 index 0000000..bf4125f --- /dev/null +++ b/subscription-manager-support.patch @@ -0,0 +1,2540 @@ +From 9849810143193393bbf6fecfca2b415a4c58e147 Mon Sep 17 00:00:00 2001 +From: Kalev Lember +Date: Fri, 28 Jun 2019 17:14:36 +0200 +Subject: [PATCH] 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 +