From 015fd59e65295e8507f7df389833a6ba443c463f Mon Sep 17 00:00:00 2001 From: Felipe Borges Date: Thu, 30 Jan 2025 13:53:29 +0100 Subject: [PATCH] system: Add subscription manager (Registration) page https://issues.redhat.com/browse/RHEL-26117 --- panels/system/cc-system-panel.c | 19 + panels/system/cc-system-panel.ui | 13 + panels/system/gnome-system-panel.desktop.in | 2 +- .../icons/scalable/actions/key-symbolic.svg | 4 + panels/system/meson.build | 4 + panels/system/subman/cc-subman-page.c | 629 ++++++++++++++++++ panels/system/subman/cc-subman-page.h | 38 ++ panels/system/subman/cc-subman-page.ui | 254 +++++++ .../subman/gnome-subman-panel.desktop.in | 14 + panels/system/subman/meson.build | 9 + panels/system/system.gresource.xml | 3 + 11 files changed, 988 insertions(+), 1 deletion(-) create mode 100644 panels/system/icons/scalable/actions/key-symbolic.svg create mode 100644 panels/system/subman/cc-subman-page.c create mode 100644 panels/system/subman/cc-subman-page.h create mode 100644 panels/system/subman/cc-subman-page.ui create mode 100644 panels/system/subman/gnome-subman-panel.desktop.in create mode 100644 panels/system/subman/meson.build diff --git a/panels/system/cc-system-panel.c b/panels/system/cc-system-panel.c index f28a96372..e7ff45bdf 100644 --- a/panels/system/cc-system-panel.c +++ b/panels/system/cc-system-panel.c @@ -35,6 +35,8 @@ #include "secure-shell/cc-secure-shell-page.h" #include "users/cc-users-page.h" +#include "subman/cc-subman-page.h" + struct _CcSystemPanel { CcPanel parent_instance; @@ -47,6 +49,8 @@ struct _CcSystemPanel CcSecureShellPage *secure_shell_dialog; AdwNavigationPage *software_updates_group; + + AdwActionRow *subman_row; }; CC_PANEL_REGISTER (CcSystemPanel, cc_system_panel) @@ -145,6 +149,8 @@ cc_system_panel_class_init (CcSystemPanelClass *klass) gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, users_row); gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, software_updates_group); + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, subman_row); + gtk_widget_class_bind_template_callback (widget_class, cc_system_page_open_software_update); gtk_widget_class_bind_template_callback (widget_class, on_secure_shell_row_clicked); @@ -161,6 +167,7 @@ static void cc_system_panel_init (CcSystemPanel *self) { CcServiceState service_state; + CcSubmanPage *subman_page; g_resources_register (cc_system_get_resource ()); gtk_widget_init_template (GTK_WIDGET (self)); @@ -176,4 +183,16 @@ cc_system_panel_init (CcSystemPanel *self) cc_panel_add_static_subpage (CC_PANEL (self), "region", CC_TYPE_REGION_PAGE); cc_panel_add_static_subpage (CC_PANEL (self), "remote-desktop", CC_TYPE_REMOTE_DESKTOP_PAGE); cc_panel_add_static_subpage (CC_PANEL (self), "users", CC_TYPE_USERS_PAGE); + + /* Subscription Manager */ + subman_page = g_object_new (CC_TYPE_SUBMAN_PAGE, NULL); + if (cc_subman_page_is_available (subman_page)) + { + cc_panel_add_subpage (CC_PANEL (self), "subman", ADW_NAVIGATION_PAGE (subman_page)); + g_object_bind_property (subman_page, + "is-available", + self->subman_row, + "visible", + G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT); + } } diff --git a/panels/system/cc-system-panel.ui b/panels/system/cc-system-panel.ui index 6de8b22e8..507c6efc3 100644 --- a/panels/system/cc-system-panel.ui +++ b/panels/system/cc-system-panel.ui @@ -70,6 +70,19 @@ + + + False + Registration + Enable Red Hat updates, content, and services + True + key-symbolic + True + navigation.push + 'subman' + + + _About diff --git a/panels/system/gnome-system-panel.desktop.in b/panels/system/gnome-system-panel.desktop.in index a437f8b20..45e3d9a50 100644 --- a/panels/system/gnome-system-panel.desktop.in +++ b/panels/system/gnome-system-panel.desktop.in @@ -11,4 +11,4 @@ StartupNotify=true Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;X-GNOME-DetailsSettings; OnlyShowIn=GNOME;Unity; # Translators: Search terms to find the System panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! -Keywords=device;system;information;details;hostname;memory;processor;version;software;operating;os;model;language;region;country;formats;numbers;units;clock;timezone;date;location;remote;desktop;rdp;vnc;ssh; +Keywords=device;system;information;details;hostname;memory;processor;version;software;operating;os;model;language;region;country;formats;numbers;units;clock;timezone;date;location;remote;desktop;rdp;vnc;ssh;Registration;Subscription;Red Hat;Products;Updates;Registry;Account;Activation;Register;Subscribe; diff --git a/panels/system/icons/scalable/actions/key-symbolic.svg b/panels/system/icons/scalable/actions/key-symbolic.svg new file mode 100644 index 000000000..7a5e13619 --- /dev/null +++ b/panels/system/icons/scalable/actions/key-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/panels/system/meson.build b/panels/system/meson.build index bbe004837..7ff9646c3 100644 --- a/panels/system/meson.build +++ b/panels/system/meson.build @@ -58,6 +58,8 @@ sources = files( 'users/pw-utils.c', 'users/run-passwd.c', 'users/user-utils.c', + + 'subman/cc-subman-page.c', ) sources += gnome.compile_resources( @@ -136,6 +138,8 @@ subdir('remote-desktop') subdir('secure-shell') subdir('users') +subdir('subman') + panels_libs += static_library( cappletname, sources: sources, diff --git a/panels/system/subman/cc-subman-page.c b/panels/system/subman/cc-subman-page.c new file mode 100644 index 000000000..7b29c3f72 --- /dev/null +++ b/panels/system/subman/cc-subman-page.c @@ -0,0 +1,629 @@ +/* + * cc-subman-page.c + * + * Copyright 2025 Red Hat Inc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Author(s): + * Felipe Borges + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-subman-page" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "cc-subman-page.h" + +#include "cc-list-row.h" +#include "cc-list-row-info-button.h" + +#include +#include +#include +#include + +#define SERVER_URL "subscription.rhsm.redhat.com" +#define DBUS_TIMEOUT 300000 /* 5 minutes */ + +typedef enum { + GSD_SUBMAN_SUBSCRIPTION_STATUS_NOT_READ = -1, + 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; + +struct _CcSubmanPage { + AdwNavigationPage parent_instance; + + AdwNavigationView *navigation; + + GtkStack *registration_page_stack; + GtkWidget *register_button; + + GtkCheckButton *username_method; + AdwEntryRow *server_row; + + AdwEntryRow *username_row; + AdwPasswordEntryRow *password_row; + AdwEntryRow *activation_keys_row; + AdwEntryRow *organization_row; + CcListRowInfoButton *organization_info_text; + + AdwPreferencesGroup *products_group; + + /* Backend */ + GDBusProxy *proxy; + gboolean is_registered; + gboolean is_available; + + GPtrArray *products; + + GCancellable *cancellable; +}; + +enum { + PROP_0, + PROP_IS_AVAILABLE, + PROP_IS_REGISTERED, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES] = { NULL }; + +G_DEFINE_TYPE (CcSubmanPage, cc_subman_page, ADW_TYPE_NAVIGATION_PAGE) + +typedef struct +{ + gchar *product_name; + gchar *product_id; + gchar *version; + gchar *arch; + 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->starts); + g_free (product->ends); + g_free (product); +} +G_DEFINE_AUTOPTR_CLEANUP_FUNC (ProductData, product_data_free); + +static void get_subscription_status (CcSubmanPage *self); + +static void +show_error (CcSubmanPage *self, + const gchar *message, + const gchar *description) +{ + AdwDialog *dialog = adw_alert_dialog_new (message, description); + + adw_alert_dialog_add_responses (ADW_ALERT_DIALOG (dialog), + "close", _("Close"), NULL); + + adw_alert_dialog_set_default_response (ADW_ALERT_DIALOG (dialog), "close"); + adw_alert_dialog_set_close_response (ADW_ALERT_DIALOG (dialog), "close"); + + g_signal_connect (dialog, "response", G_CALLBACK (adw_dialog_close), NULL); + + adw_dialog_present (dialog, GTK_WIDGET (self)); +} + +static void +unregistration_done_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + CcSubmanPage *self = CC_SUBMAN_PAGE (user_data); + g_autoptr(GVariant) variant_results = NULL; + g_autoptr(GError) error = NULL; + + gtk_widget_set_sensitive (GTK_WIDGET (self), TRUE); + + variant_results = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), result, &error); + if (variant_results == NULL) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_dbus_error_strip_remote_error (error); + show_error (self, _("Failed to Unregister System"), error->message); + return; + } + + g_object_set (self, "is-registered", FALSE, NULL); + g_debug ("System is no longer registered"); +} + +static void +on_unregister_response_cb (AdwAlertDialog *dialog, + GAsyncResult *result, + CcSubmanPage *self) +{ + const gchar *response = adw_alert_dialog_choose_finish (dialog, result); + + if (g_strcmp0 (response, "unregister") == 0) { + g_dbus_proxy_call (self->proxy, + "Unregister", + NULL, + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + self->cancellable, + unregistration_done_cb, + self); + } else { + gtk_widget_set_sensitive (GTK_WIDGET (self), TRUE); + } +} + +static void +unregister (CcSubmanPage *self) +{ + AdwDialog *dialog = adw_alert_dialog_new (_("Remove Registration?"), + _("Subscriptions will be removed and the device will no longer receive software updates")); + adw_alert_dialog_add_responses (ADW_ALERT_DIALOG (dialog), + "cancel", _("Cancel"), + "unregister", _("Remove Registration"), NULL); + adw_alert_dialog_set_response_appearance (ADW_ALERT_DIALOG (dialog), + "unregister", + ADW_RESPONSE_DESTRUCTIVE); + adw_alert_dialog_set_default_response (ADW_ALERT_DIALOG (dialog), "cancel"); + adw_alert_dialog_set_close_response (ADW_ALERT_DIALOG (dialog), "cancel"); + + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + + adw_alert_dialog_choose (ADW_ALERT_DIALOG (dialog), + GTK_WIDGET (self), + self->cancellable, + (GAsyncReadyCallback) on_unregister_response_cb, + self); +} + +static void +add_product_row (GtkWidget *expander, + const gchar *title, + const gchar *value) +{ + CcListRow *row = g_object_new (CC_TYPE_LIST_ROW, NULL); + + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), title); + cc_list_row_set_secondary_label (row, value); + + adw_expander_row_add_row (ADW_EXPANDER_ROW (expander), GTK_WIDGET (row)); +} + +static void +add_product (CcSubmanPage *self, + ProductData *product) +{ + GtkWidget *product_row; + + product_row = adw_expander_row_new (); + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (product_row), product->product_name); + + add_product_row (product_row, _("Product ID"), product->product_id); + add_product_row (product_row, _("Version"), product->version); + add_product_row (product_row, _("Arch"), product->arch); + + if (product->starts[0] != '\0' && product->ends[0] != '\0') { + add_product_row (product_row, _("Starts"), product->starts); + add_product_row (product_row, _("Ends"), product->ends); + } + + adw_preferences_group_add (self->products_group, product_row); +} + +static ProductData * +parse_product_variant (GVariant *product_variant) +{ + 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, "starts", "s", &product->starts); + g_variant_dict_lookup (&dict, "ends", "s", &product->ends); + + return g_steal_pointer (&product); +} + +static void +fetch_installed_products (CcSubmanPage *self) +{ + g_autoptr(GVariant) installed_products_variant = NULL; + GVariantIter iter_array; + GVariant *child; + + installed_products_variant = g_dbus_proxy_get_cached_property (self->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)); + } + + if (self->products == NULL || self->products->len == 0) { + // Show a no-products page? + } + + for (guint i = 0; i < self->products->len; i++) { + ProductData *product = g_ptr_array_index (self->products, i); + + add_product (self, product); + } +} + +static void +registration_done_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + CcSubmanPage *self = CC_SUBMAN_PAGE (user_data); + g_autoptr(GVariant) results = NULL; + g_autoptr(GError) error = NULL; + + gtk_widget_set_sensitive (GTK_WIDGET (self), TRUE); + + results = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), result, &error); + if (results == NULL) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_dbus_error_strip_remote_error (error); + + show_error (self, _("Failed to Register System"), error->message); + + g_warning ("Failed to register: %s", error->message); + return; + } + + g_debug ("Registration successful"); + get_subscription_status (self); +} + +static void +update_register_button (CcSubmanPage *self) +{ + gboolean is_registering = !gtk_widget_get_sensitive (GTK_WIDGET (self)); + + gtk_widget_set_visible (self->register_button, !is_registering); +} + +static void +register_subscription (CcSubmanPage *self) +{ + gboolean register_with_username = gtk_check_button_get_active (self->username_method); + g_autoptr(GVariantBuilder) options_builder = NULL; + const gchar *hostname; + const gchar *organization; + const gchar *username; + const gchar *password; + const gchar *activation_keys; + + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + + hostname = gtk_editable_get_text (GTK_EDITABLE (self->server_row)); + organization = gtk_editable_get_text (GTK_EDITABLE (self->organization_row)); + + if (register_with_username) { + username = gtk_editable_get_text (GTK_EDITABLE (self->username_row)); + password = gtk_editable_get_text (GTK_EDITABLE (self->password_row)); + } else { + activation_keys = gtk_editable_get_text (GTK_EDITABLE (self->activation_keys_row)); + } + + options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + 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)); + if (register_with_username) { + g_variant_builder_add (options_builder, "{sv}", "kind", g_variant_new_string ("username")); + 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)); + } else { + g_variant_builder_add (options_builder, "{sv}", "kind", g_variant_new_string ("key")); + g_variant_builder_add (options_builder, "{sv}", "activation-key", g_variant_new_string (activation_keys)); + } + + g_dbus_proxy_call (self->proxy, + "Register", + g_variant_new ("(a{sv})", options_builder), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + self->cancellable, + registration_done_cb, + self); +} + +static void +update_for_status (CcSubmanPage *self, + GsdSubmanSubscriptionStatus status) +{ + switch (status) { + case GSD_SUBMAN_SUBSCRIPTION_STATUS_NOT_READ: + g_object_set (G_OBJECT (self), "is-registered", FALSE, NULL); + break;// Why return?; + case GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN: + g_object_set (G_OBJECT (self), "is-registered", FALSE, NULL); + break; + case GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED: + g_object_set (G_OBJECT (self), "is-registered", TRUE, NULL); + break; + case GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID: + case GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID: + g_object_set (G_OBJECT (self), "is-registered", TRUE, NULL); + break; + case GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID: + g_object_set (G_OBJECT (self), "is-registered", TRUE, NULL); + break; + case GSD_SUBMAN_SUBSCRIPTION_STATUS_NO_INSTALLED_PRODUCTS: + g_object_set (G_OBJECT (self), "is-registered", FALSE, NULL); + break; + default: + g_assert_not_reached (); + } +} + +static void +get_subscription_status (CcSubmanPage *self) +{ + g_autoptr(GVariant) status_variant = NULL; + g_autoptr(GError) error = NULL; + guint32 u; + + if (self->proxy == NULL) { + self->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 proxy for org.gnome.SettingsDaemon.Subscription: %s", error->message); + + self->is_available = FALSE; + return; + } + + g_signal_connect_swapped (self->proxy, "g-properties-changed", + G_CALLBACK (get_subscription_status), self); + } + + status_variant = g_dbus_proxy_get_cached_property (self->proxy, "SubscriptionStatus"); + if (!status_variant) { + g_debug ("Unable to get SubscriptionStatus property"); + + self->is_available = FALSE; + return; + } + + g_debug ("Subscription manager is available"); + self->is_available = TRUE; + + g_variant_get (status_variant, "u", &u); + update_for_status (self, u); +} + +static void +validate_registration_details (CcSubmanPage *self) +{ + gboolean username_auth = gtk_check_button_get_active (self->username_method); + const gchar *server_url = gtk_editable_get_text (GTK_EDITABLE (self->server_row)); + gboolean can_register = FALSE; + + can_register = (g_strcmp0 (server_url, "") != 0); + if (username_auth) { + const gchar *username = gtk_editable_get_text (GTK_EDITABLE (self->username_row)); + const gchar *password = gtk_editable_get_text (GTK_EDITABLE (self->password_row)); + + can_register = can_register && (g_strcmp0 (username, "") != 0) && (g_strcmp0 (password, "") != 0); + } else { + const gchar *activation_keys = gtk_editable_get_text (GTK_EDITABLE (self->activation_keys_row)); + const gchar *organization = gtk_editable_get_text (GTK_EDITABLE (self->organization_row)); + + can_register = can_register && (g_strcmp0 (activation_keys, "") != 0) && (g_strcmp0 (organization, "") != 0); + } + + gtk_widget_set_sensitive (self->register_button, can_register); +} + +static void +on_is_registered_changed (CcSubmanPage *self) +{ + adw_navigation_view_replace (self->navigation, NULL, 0); + adw_navigation_view_push_by_tag (self->navigation, + self->is_registered ? "products-page" : "registration-page"); +} + +static void +update_organization_info_text (CcSubmanPage *self) +{ + gboolean username_auth = gtk_check_button_get_active (self->username_method); + + cc_list_row_info_button_set_text (self->organization_info_text, + username_auth ? _("If the user account belongs to multiple organizations, an organization must be specified.") : + _("Organization must be specified when registering with an activation key.")); +} + +static void +reset_server_row_button_clicked_cb (CcSubmanPage *self) +{ + gtk_editable_set_text (GTK_EDITABLE (self->server_row), SERVER_URL); +} + +static void +on_register_system_button_clicked_cb (CcSubmanPage *self) +{ + gtk_stack_set_visible_child_name (self->registration_page_stack, "registration"); +} + +static void +cc_subman_page_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcSubmanPage *self = CC_SUBMAN_PAGE (object); + + switch (prop_id) { + case PROP_IS_REGISTERED: + self->is_registered = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_subman_page_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcSubmanPage *self = CC_SUBMAN_PAGE (object); + + switch (prop_id) { + case PROP_IS_AVAILABLE: + g_value_set_boolean (value, self->is_available); + break; + case PROP_IS_REGISTERED: + g_value_set_boolean (value, self->is_registered); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_subman_page_dispose (GObject *object) +{ + CcSubmanPage *self = CC_SUBMAN_PAGE (object); + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + + g_clear_object (&self->proxy); + + G_OBJECT_CLASS (cc_subman_page_parent_class)->dispose (object); +} + +static void +cc_subman_page_init (CcSubmanPage *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_editable_set_text (GTK_EDITABLE (self->server_row), SERVER_URL); + update_organization_info_text (self); + self->products = g_ptr_array_new_with_free_func ((GDestroyNotify) product_data_free); + + g_signal_connect_swapped (G_OBJECT (self), + "notify::is-registered", + G_CALLBACK (on_is_registered_changed), + self); + + self->cancellable = g_cancellable_new (); +} + +static void +cc_subman_page_class_init (CcSubmanPageClass * klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = cc_subman_page_set_property; + object_class->get_property = cc_subman_page_get_property; + object_class->finalize = cc_subman_page_dispose; + + properties[PROP_IS_AVAILABLE] = + g_param_spec_boolean ("is-available", + "Subscription Manager is available", + "Whether Subscription Manager service is available in the system.", + FALSE, + G_PARAM_READABLE); + properties[PROP_IS_REGISTERED] = + g_param_spec_boolean ("is-registered", + "System is registered", + "The system is registered with subscription manager", + FALSE, + G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + g_type_ensure (CC_TYPE_LIST_ROW); + g_type_ensure (CC_TYPE_LIST_ROW_INFO_BUTTON); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/system/subman/cc-subman-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, navigation); + + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, username_method); + + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, server_row); + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, organization_row); + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, organization_info_text); + + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, username_row); + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, password_row); + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, activation_keys_row); + + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, register_button); + + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, registration_page_stack); + gtk_widget_class_bind_template_child (widget_class, CcSubmanPage, products_group); + + gtk_widget_class_bind_template_callback (widget_class, validate_registration_details); + gtk_widget_class_bind_template_callback (widget_class, register_subscription); + gtk_widget_class_bind_template_callback (widget_class, unregister); + gtk_widget_class_bind_template_callback (widget_class, reset_server_row_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, update_organization_info_text); + gtk_widget_class_bind_template_callback (widget_class, update_register_button); + gtk_widget_class_bind_template_callback (widget_class, on_register_system_button_clicked_cb); +} + +gboolean +cc_subman_page_is_available (CcSubmanPage *self) +{ + get_subscription_status (self); + + fetch_installed_products (self); + + return self->is_available; +} diff --git a/panels/system/subman/cc-subman-page.h b/panels/system/subman/cc-subman-page.h new file mode 100644 index 000000000..875b1e57a --- /dev/null +++ b/panels/system/subman/cc-subman-page.h @@ -0,0 +1,38 @@ +/* + * cc-subman-page.h + * + * Copyright 2025 Red Hat Inc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Author(s): + * Felipe Borges + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define CC_TYPE_SUBMAN_PAGE (cc_subman_page_get_type ()) + +G_DECLARE_FINAL_TYPE (CcSubmanPage, cc_subman_page, CC, SUBMAN_PAGE, AdwNavigationPage) + +gboolean cc_subman_page_is_available (CcSubmanPage *self); + +G_END_DECLS + diff --git a/panels/system/subman/cc-subman-page.ui b/panels/system/subman/cc-subman-page.ui new file mode 100644 index 000000000..025f23172 --- /dev/null +++ b/panels/system/subman/cc-subman-page.ui @@ -0,0 +1,254 @@ + + + + diff --git a/panels/system/subman/gnome-subman-panel.desktop.in b/panels/system/subman/gnome-subman-panel.desktop.in new file mode 100644 index 000000000..ca67b33af --- /dev/null +++ b/panels/system/subman/gnome-subman-panel.desktop.in @@ -0,0 +1,14 @@ +[Desktop Entry] +Name=Registration +Comment=Enable Red Hat updates, content, and services +Exec=gnome-control-center system subman +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=key-symbolic +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;X-GNOME-SystemSettings; +OnlyShowIn=GNOME;Unity; +# Translators: Search terms to find the Users panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=Registration;Subscription;Red Hat;Products;Updates;Registry;Account;Activation;Register;Subscribe; diff --git a/panels/system/subman/meson.build b/panels/system/subman/meson.build new file mode 100644 index 000000000..58a99ee49 --- /dev/null +++ b/panels/system/subman/meson.build @@ -0,0 +1,9 @@ +desktop = 'gnome-subman-panel.desktop' +i18n.merge_file( + type: 'desktop', + input: desktop + '.in', + output: desktop, + po_dir: po_dir, + install: true, + install_dir: control_center_desktopdir +) diff --git a/panels/system/system.gresource.xml b/panels/system/system.gresource.xml index acb4169b1..3d5c0684e 100644 --- a/panels/system/system.gresource.xml +++ b/panels/system/system.gresource.xml @@ -29,9 +29,12 @@ users/cc-user-page.ui users/data/join-dialog.ui users/users.css + + subman/cc-subman-page.ui + icons/scalable/actions/key-symbolic.svg icons/scalable/actions/system-update-symbolic.svg icons/scalable/status/fingerprint-detection-complete-symbolic.svg icons/scalable/status/fingerprint-detection-symbolic.svg -- 2.47.0