diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1284af --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/gnome-control-center-3.28.2.tar.xz diff --git a/.gnome-control-center.metadata b/.gnome-control-center.metadata new file mode 100644 index 0000000..a7456aa --- /dev/null +++ b/.gnome-control-center.metadata @@ -0,0 +1 @@ +68f77d7fd2921025a65d0b0904e6db018ca7c1d0 SOURCES/gnome-control-center-3.28.2.tar.xz diff --git a/SOURCES/0001-common-fix-udev-based-device-removal.patch b/SOURCES/0001-common-fix-udev-based-device-removal.patch new file mode 100644 index 0000000..4032f3e --- /dev/null +++ b/SOURCES/0001-common-fix-udev-based-device-removal.patch @@ -0,0 +1,75 @@ +From 6b34f996699a80c249d2cccfe369b3b61e70d4ce Mon Sep 17 00:00:00 2001 +From: Peter Hutterer +Date: Mon, 10 Dec 2018 14:43:30 +1000 +Subject: [PATCH] common: fix udev-based device removal + +libgudev allocs a new GUdevDevice object for each event, so the pointer value +for the 'add' udev event differs from the one for the 'remove' event. If we +use the pointer value as hash table key, we'll never remove the device. +Switch to use the syspath of the device instead, that one is unique per +device. + +Fixes #309 +--- + panels/common/gsd-device-manager-udev.c | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +diff --git a/panels/common/gsd-device-manager-udev.c b/panels/common/gsd-device-manager-udev.c +index aa9304232..105c8e987 100644 +--- a/panels/common/gsd-device-manager-udev.c ++++ b/panels/common/gsd-device-manager-udev.c +@@ -122,6 +122,7 @@ add_device (GsdUdevDeviceManager *manager, + { + GUdevDevice *parent; + GsdDevice *device; ++ gchar *syspath; + + parent = g_udev_device_get_parent (udev_device); + +@@ -129,7 +130,8 @@ add_device (GsdUdevDeviceManager *manager, + return; + + device = create_device (udev_device); +- g_hash_table_insert (manager->devices, g_object_ref (udev_device), device); ++ syspath = g_strdup (g_udev_device_get_sysfs_path (udev_device)); ++ g_hash_table_insert (manager->devices, syspath, device); + g_signal_emit_by_name (manager, "device-added", device); + } + +@@ -138,17 +140,19 @@ remove_device (GsdUdevDeviceManager *manager, + GUdevDevice *udev_device) + { + GsdDevice *device; ++ gchar *syspath; + +- device = g_hash_table_lookup (manager->devices, udev_device); ++ syspath = g_strdup (g_udev_device_get_sysfs_path (udev_device)); ++ device = g_hash_table_lookup (manager->devices, syspath); + + if (!device) + return; + +- g_hash_table_steal (manager->devices, udev_device); ++ g_hash_table_steal (manager->devices, syspath); + g_signal_emit_by_name (manager, "device-removed", device); + + g_object_unref (device); +- g_object_unref (udev_device); ++ g_free (syspath); + } + + static void +@@ -173,8 +177,8 @@ gsd_udev_device_manager_init (GsdUdevDeviceManager *manager) + const gchar *subsystems[] = { "input", NULL }; + GList *devices, *l; + +- manager->devices = g_hash_table_new_full (NULL, NULL, +- (GDestroyNotify) g_object_unref, ++ manager->devices = g_hash_table_new_full (g_str_hash, g_str_equal, ++ (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + + manager->udev_client = g_udev_client_new (subsystems); +-- +2.24.0 + diff --git a/SOURCES/0001-info-Add-subscription-manager-integration.patch b/SOURCES/0001-info-Add-subscription-manager-integration.patch new file mode 100644 index 0000000..609a769 --- /dev/null +++ b/SOURCES/0001-info-Add-subscription-manager-integration.patch @@ -0,0 +1,2258 @@ +From 0ce01af0e0e58d81f998093f5a8a068539f56c12 Mon Sep 17 00:00:00 2001 +From: Kalev Lember +Date: Fri, 28 Jun 2019 17:14:36 +0200 +Subject: [PATCH] info: Add subscription manager integration + +--- + panels/info/cc-info-overview-panel.c | 157 ++++- + panels/info/cc-subscription-details-dialog.c | 407 ++++++++++++ + panels/info/cc-subscription-details-dialog.h | 32 + + panels/info/cc-subscription-details-dialog.ui | 248 +++++++ + panels/info/cc-subscription-register-dialog.c | 403 +++++++++++ + panels/info/cc-subscription-register-dialog.h | 32 + + .../info/cc-subscription-register-dialog.ui | 623 ++++++++++++++++++ + panels/info/info-overview.ui | 138 +++- + panels/info/info.gresource.xml | 2 + + panels/info/meson.build | 4 + + po/POTFILES.in | 4 + + 11 files changed, 2038 insertions(+), 12 deletions(-) + create mode 100644 panels/info/cc-subscription-details-dialog.c + create mode 100644 panels/info/cc-subscription-details-dialog.h + create mode 100644 panels/info/cc-subscription-details-dialog.ui + create mode 100644 panels/info/cc-subscription-register-dialog.c + create mode 100644 panels/info/cc-subscription-register-dialog.h + create mode 100644 panels/info/cc-subscription-register-dialog.ui + +diff --git a/panels/info/cc-info-overview-panel.c b/panels/info/cc-info-overview-panel.c +index 2256b730c..1467060f9 100644 +--- a/panels/info/cc-info-overview-panel.c ++++ b/panels/info/cc-info-overview-panel.c +@@ -24,6 +24,8 @@ + #include "shell/cc-hostname-entry.h" + + #include "cc-info-resources.h" ++#include "cc-subscription-details-dialog.h" ++#include "cc-subscription-register-dialog.h" + #include "info-cleanup.h" + + #include +@@ -68,6 +70,10 @@ typedef struct + GtkWidget *disk_label; + GtkWidget *graphics_label; + GtkWidget *virt_type_label; ++ GtkWidget *subscription_stack; ++ GtkWidget *details_button; ++ GtkWidget *register_button; ++ GtkWidget *updates_separator; + GtkWidget *updates_button; + + /* Virtualisation labels */ +@@ -86,6 +92,8 @@ typedef struct + guint64 total_bytes; + + GraphicsData *graphics_data; ++ ++ GDBusProxy *subscription_proxy; + } CcInfoOverviewPanelPrivate; + + struct _CcInfoOverviewPanel +@@ -586,7 +594,6 @@ get_primary_disc_info (CcInfoOverviewPanel *self) + g_list_free (points); + g_hash_table_destroy (hash); + +- priv->cancellable = g_cancellable_new (); + get_primary_disc_info_start (self); + } + +@@ -793,6 +800,137 @@ info_overview_panel_setup_overview (CcInfoOverviewPanel *self) + gtk_label_set_markup (GTK_LABEL (priv->graphics_label), priv->graphics_data->hardware_string); + } + ++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_LAST ++} GsdSubmanSubscriptionStatus; ++ ++static gboolean ++get_subscription_status (CcInfoOverviewPanel *self, GsdSubmanSubscriptionStatus *status) ++{ ++ CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self); ++ g_autoptr(GVariant) status_variant = NULL; ++ guint32 u; ++ ++ status_variant = g_dbus_proxy_get_cached_property (priv->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; ++} ++ ++static void ++reload_subscription_status (CcInfoOverviewPanel *self) ++{ ++ CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self); ++ GsdSubmanSubscriptionStatus status; ++ ++ if (priv->subscription_proxy == NULL) ++ { ++ gtk_widget_hide (priv->subscription_stack); ++ return; ++ } ++ ++ if (!get_subscription_status (self, &status)) ++ { ++ gtk_widget_hide (priv->subscription_stack); ++ return; ++ } ++ ++ switch (status) ++ { ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN: ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID: ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED: ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID: ++ gtk_stack_set_visible_child_name (GTK_STACK (priv->subscription_stack), "not-registered"); ++ gtk_widget_set_sensitive (priv->updates_button, FALSE); ++ break; ++ ++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID: ++ gtk_stack_set_visible_child_name (GTK_STACK (priv->subscription_stack), "registered"); ++ gtk_widget_set_sensitive (priv->updates_button, TRUE); ++ break; ++ ++ default: ++ g_assert_not_reached (); ++ break; ++ } ++} ++ ++static void ++on_details_button_clicked (GtkWidget *widget, ++ CcInfoOverviewPanel *self) ++{ ++ CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self); ++ CcSubscriptionDetailsDialog *dialog; ++ GtkWindow *toplevel; ++ ++ dialog = cc_subscription_details_dialog_new (priv->subscription_proxy); ++ toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))); ++ gtk_window_set_transient_for (GTK_WINDOW (dialog), toplevel); ++ ++ gtk_dialog_run (GTK_DIALOG (dialog)); ++ gtk_widget_destroy (GTK_WIDGET (dialog)); ++ ++ reload_subscription_status (self); ++} ++ ++static void ++on_register_button_clicked (GtkWidget *widget, ++ CcInfoOverviewPanel *self) ++{ ++ CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self); ++ CcSubscriptionRegisterDialog *dialog; ++ GtkWindow *toplevel; ++ ++ dialog = cc_subscription_register_dialog_new (priv->subscription_proxy); ++ toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))); ++ gtk_window_set_transient_for (GTK_WINDOW (dialog), toplevel); ++ ++ gtk_dialog_run (GTK_DIALOG (dialog)); ++ gtk_widget_destroy (GTK_WIDGET (dialog)); ++ ++ reload_subscription_status (self); ++} ++ ++static void ++info_overview_panel_setup_subscriptions (CcInfoOverviewPanel *self) ++{ ++ CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self); ++ g_autoptr(GError) error = NULL; ++ ++ priv->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 (priv->details_button, "clicked", G_CALLBACK (on_details_button_clicked), self); ++ g_signal_connect (priv->register_button, "clicked", G_CALLBACK (on_register_button_clicked), self); ++ ++ reload_subscription_status (self); ++} ++ + static gboolean + does_gnome_software_exist (void) + { +@@ -856,6 +994,8 @@ cc_info_overview_panel_finalize (GObject *object) + g_free (priv->gnome_date); + g_free (priv->gnome_distributor); + ++ g_clear_object (&priv->subscription_proxy); ++ + G_OBJECT_CLASS (cc_info_overview_panel_parent_class)->finalize (object); + } + +@@ -880,6 +1020,10 @@ cc_info_overview_panel_class_init (CcInfoOverviewPanelClass *klass) + gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, disk_label); + gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, graphics_label); + gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, virt_type_label); ++ gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, subscription_stack); ++ gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, details_button); ++ gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, register_button); ++ gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, updates_separator); + gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, updates_button); + gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, label8); + gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, grid1); +@@ -897,15 +1041,24 @@ cc_info_overview_panel_init (CcInfoOverviewPanel *self) + + g_resources_register (cc_info_get_resource ()); + ++ priv->cancellable = g_cancellable_new (); ++ + priv->graphics_data = get_graphics_data (); + + if (does_gnome_software_exist () || does_gpk_update_viewer_exist ()) + g_signal_connect (priv->updates_button, "clicked", G_CALLBACK (on_updates_button_clicked), self); + else +- gtk_widget_destroy (priv->updates_button); ++ gtk_widget_hide (priv->updates_button); + + info_overview_panel_setup_overview (self); + info_overview_panel_setup_virt (self); ++ info_overview_panel_setup_subscriptions (self); ++ ++ /* show separator when both items are visible */ ++ if (gtk_widget_get_visible (priv->subscription_stack) && gtk_widget_get_visible (priv->updates_button)) ++ gtk_widget_show (priv->updates_separator); ++ else ++ gtk_widget_hide (priv->updates_separator); + } + + GtkWidget * +diff --git a/panels/info/cc-subscription-details-dialog.c b/panels/info/cc-subscription-details-dialog.c +new file mode 100644 +index 000000000..1931ced81 +--- /dev/null ++++ b/panels/info/cc-subscription-details-dialog.c +@@ -0,0 +1,407 @@ ++/* -*- 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" ++ ++#define DBUS_TIMEOUT 300000 /* 5 minutes */ ++ ++typedef enum { ++ DIALOG_STATE_SHOW_DETAILS, ++ 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; ++ GtkButton *header_unregister_button; ++ GtkRevealer *notification_revealer; ++ GtkLabel *error_label; ++ GtkStack *stack; ++ GtkBox *products_box1; ++ GtkBox *products_box2; ++ GtkButton *unregister_button; ++}; ++ ++G_DEFINE_TYPE (CcSubscriptionDetailsDialog, cc_subscription_details_dialog, GTK_TYPE_DIALOG); ++ ++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) ++{ ++ GtkGrid *product_grid; ++ const gchar *status_text; ++ ++ if (g_strcmp0 (product->status, "subscribed") == 0) ++ status_text = _("Subscribed"); ++ else ++ status_text = _("Not Subscribed (Not supported by a valid subscription.)"); ++ ++ 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); ++ 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))); ++ ++ 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_hide (GTK_WIDGET (self->back_button)); ++ gtk_widget_hide (GTK_WIDGET (self->header_unregister_button)); ++ ++ gtk_stack_set_visible_child_name (self->stack, "show-details"); ++ 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_widget_show (GTK_WIDGET (self->header_unregister_button)); ++ ++ 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_widget_show (GTK_WIDGET (self->header_unregister_button)); ++ ++ 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)); ++ ++ 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.")); ++ gtk_widget_show (w1); ++ gtk_widget_show (w2); ++ gtk_container_add (GTK_CONTAINER (self->products_box1), w1); ++ gtk_container_add (GTK_CONTAINER (self->products_box2), w2); ++ return; ++ } ++ ++ 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); ++ GtkWidget *w2 = add_product (self, product); ++ gtk_container_add (GTK_CONTAINER (self->products_box1), w1); ++ gtk_container_add (GTK_CONTAINER (self->products_box2), w2); ++ } ++} ++ ++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 ++load_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 ++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 ++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->cancellable = g_cancellable_new (); ++ 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/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_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, products_box1); ++ gtk_widget_class_bind_template_child (widget_class, CcSubscriptionDetailsDialog, products_box2); ++ 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_unregister_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); ++} ++ ++CcSubscriptionDetailsDialog * ++cc_subscription_details_dialog_new (GDBusProxy *subscription_proxy) ++{ ++ CcSubscriptionDetailsDialog *self; ++ ++ self = g_object_new (CC_TYPE_SUBSCRIPTION_DETAILS_DIALOG, "use-header-bar", TRUE, NULL); ++ self->subscription_proxy = g_object_ref (subscription_proxy); ++ ++ load_installed_products (self); ++ dialog_reload (self); ++ ++ return self; ++} +diff --git a/panels/info/cc-subscription-details-dialog.h b/panels/info/cc-subscription-details-dialog.h +new file mode 100644 +index 000000000..a61a22838 +--- /dev/null ++++ b/panels/info/cc-subscription-details-dialog.h +@@ -0,0 +1,32 @@ ++/* -*- 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); ++ ++G_END_DECLS +diff --git a/panels/info/cc-subscription-details-dialog.ui b/panels/info/cc-subscription-details-dialog.ui +new file mode 100644 +index 000000000..6f0b16930 +--- /dev/null ++++ b/panels/info/cc-subscription-details-dialog.ui +@@ -0,0 +1,248 @@ ++ ++ ++ ++ +diff --git a/panels/info/cc-subscription-register-dialog.c b/panels/info/cc-subscription-register-dialog.c +new file mode 100644 +index 000000000..d7a17cc99 +--- /dev/null ++++ b/panels/info/cc-subscription-register-dialog.c +@@ -0,0 +1,403 @@ ++/* -*- 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->cancellable = g_cancellable_new (); ++ 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/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); ++} ++ ++CcSubscriptionRegisterDialog * ++cc_subscription_register_dialog_new (GDBusProxy *subscription_proxy) ++{ ++ CcSubscriptionRegisterDialog *self; ++ ++ self = g_object_new (CC_TYPE_SUBSCRIPTION_REGISTER_DIALOG, "use-header-bar", TRUE, NULL); ++ self->subscription_proxy = g_object_ref (subscription_proxy); ++ ++ return self; ++} +diff --git a/panels/info/cc-subscription-register-dialog.h b/panels/info/cc-subscription-register-dialog.h +new file mode 100644 +index 000000000..c5918df9f +--- /dev/null ++++ b/panels/info/cc-subscription-register-dialog.h +@@ -0,0 +1,32 @@ ++/* -*- 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); ++ ++G_END_DECLS +diff --git a/panels/info/cc-subscription-register-dialog.ui b/panels/info/cc-subscription-register-dialog.ui +new file mode 100644 +index 000000000..21e317b41 +--- /dev/null ++++ b/panels/info/cc-subscription-register-dialog.ui +@@ -0,0 +1,623 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ horizontal ++ ++ ++ ++ ++ ++ +diff --git a/panels/info/info-overview.ui b/panels/info/info-overview.ui +index aa87fbec2..e33ba399a 100644 +--- a/panels/info/info-overview.ui ++++ b/panels/info/info-overview.ui +@@ -4,16 +4,17 @@ +