diff options
Diffstat (limited to 'gnome-initial-setup/pages/goa')
-rw-r--r-- | gnome-initial-setup/pages/goa/gis-goa-page.c | 606 | ||||
-rw-r--r-- | gnome-initial-setup/pages/goa/gis-goa-page.css | 6 | ||||
-rw-r--r-- | gnome-initial-setup/pages/goa/gis-goa-page.h | 55 | ||||
-rw-r--r-- | gnome-initial-setup/pages/goa/gis-goa-page.ui | 54 | ||||
-rw-r--r-- | gnome-initial-setup/pages/goa/gnome-initial-setup-goa-helper.c | 505 | ||||
-rw-r--r-- | gnome-initial-setup/pages/goa/goa.gresource.xml | 8 | ||||
-rw-r--r-- | gnome-initial-setup/pages/goa/meson.build | 38 |
7 files changed, 1272 insertions, 0 deletions
diff --git a/gnome-initial-setup/pages/goa/gis-goa-page.c b/gnome-initial-setup/pages/goa/gis-goa-page.c new file mode 100644 index 0000000..b934825 --- /dev/null +++ b/gnome-initial-setup/pages/goa/gis-goa-page.c @@ -0,0 +1,606 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2012 Red Hat + * + * 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 <http://www.gnu.org/licenses/>. + * + * Written by: + * Jasper St. Pierre <jstpierre@mecheye.net> + */ + +/* Online accounts page {{{1 */ + +#define PAGE_ID "goa" + +#include "config.h" +#include "gis-goa-page.h" +#include "goa-resources.h" + +#define GOA_API_IS_SUBJECT_TO_CHANGE +#include <goa/goa.h> + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include "gis-page-header.h" + +#ifdef GDK_WINDOWING_X11 +#include <gdk/x11/gdkx.h> +#endif +#ifdef GDK_WINDOWING_WAYLAND +#include <gdk/wayland/gdkwayland.h> +#endif + +#define VENDOR_GOA_GROUP "goa" +#define VENDOR_PROVIDERS_KEY "providers" + +struct _GisGoaPagePrivate { + GtkWidget *accounts_list; + + GoaClient *goa_client; + GHashTable *providers; + gboolean accounts_exist; + char *window_export_handle; +}; +typedef struct _GisGoaPagePrivate GisGoaPagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisGoaPage, gis_goa_page, GIS_TYPE_PAGE); + +struct _ProviderWidget { + GisGoaPage *page; + GVariant *provider; + GoaAccount *displayed_account; + + GtkWidget *row; + GtkWidget *arrow_icon; +}; +typedef struct _ProviderWidget ProviderWidget; + +G_GNUC_NULL_TERMINATED +static char * +run_goa_helper_sync (const char *command, + ...) +{ + g_autoptr(GPtrArray) argv = NULL; + g_autofree char *output = NULL; + g_autoptr(GError) error = NULL; + const char *param; + va_list args; + int status; + + argv = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (argv, g_strdup (LIBEXECDIR "/gnome-initial-setup-goa-helper")); + g_ptr_array_add (argv, g_strdup (command)); + + va_start (args, command); + while ((param = va_arg (args, const char*)) != NULL) + g_ptr_array_add (argv, g_strdup (param)); + va_end (args); + + g_ptr_array_add (argv, NULL); + + if (!g_spawn_sync (NULL, + (char **) argv->pdata, + NULL, + 0, + NULL, + NULL, + &output, + NULL, + &status, + &error)) + { + g_warning ("Failed to run online accounts helper: %s", error->message); + return NULL; + } + + if (!g_spawn_check_exit_status (status, NULL)) + return NULL; + + if (output == NULL || *output == '\0') + return NULL; + + return g_steal_pointer (&output); +} + +static void +run_goa_helper_in_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + g_autofree char *output = NULL; + g_autoptr(GError) error = NULL; + GPtrArray *argv = task_data; + int status; + + g_spawn_sync (NULL, + (char **) argv->pdata, + NULL, 0, NULL, NULL, + &output, + NULL, + &status, + &error); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + if (!g_spawn_check_exit_status (status, &error)) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + g_task_return_pointer (task, g_steal_pointer (&output), g_free); +} + +static void +run_goa_helper_async (const gchar *command, + const gchar *param, + const gchar *window_handle, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GPtrArray) argv = NULL; + g_autoptr(GTask) task = NULL; + + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + argv = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (argv, g_strdup (LIBEXECDIR "/gnome-initial-setup-goa-helper")); + g_ptr_array_add (argv, g_strdup (command)); + g_ptr_array_add (argv, g_strdup (param)); + g_ptr_array_add (argv, g_strdup (window_handle)); + g_ptr_array_add (argv, NULL); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, run_goa_helper_async); + g_task_set_task_data (task, g_steal_pointer (&argv), (GDestroyNotify) g_ptr_array_unref); + g_task_run_in_thread (task, run_goa_helper_in_thread_func); +} + +static void +sync_provider_widget (ProviderWidget *provider_widget) +{ + gboolean has_account = (provider_widget->displayed_account != NULL); + + gtk_widget_set_visible (provider_widget->arrow_icon, !has_account); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (provider_widget->row), !has_account); + + adw_action_row_set_subtitle (ADW_ACTION_ROW (provider_widget->row), + has_account ? goa_account_get_presentation_identity (provider_widget->displayed_account) : NULL); + +} + +static void +on_create_account_finish_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autofree char *new_account_id = NULL; + g_autoptr(GError) error = NULL; + + new_account_id = g_task_propagate_pointer (G_TASK (result), &error); + + if (error) + g_warning ("Error creating account: %s", error->message); +} + +static void +add_account_to_provider (ProviderWidget *provider_widget) +{ + GisGoaPage *page = provider_widget->page; + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + g_autofree char *provider_type = NULL; + + if (!priv->window_export_handle) + return; + + g_variant_get (provider_widget->provider, "(ssviu)", &provider_type, NULL, NULL, NULL, NULL); + + run_goa_helper_async ("create-account", + provider_type, + priv->window_export_handle, + NULL, + on_create_account_finish_cb, + page); +} + +static gboolean +is_gicon_symbolic (GtkWidget *widget, + GIcon *icon) +{ + g_autoptr(GtkIconPaintable) icon_paintable = NULL; + GtkIconTheme *icon_theme; + + icon_theme = gtk_icon_theme_get_for_display (gdk_display_get_default ()); + icon_paintable = gtk_icon_theme_lookup_by_gicon (icon_theme, + icon, + 32, + gtk_widget_get_scale_factor (widget), + gtk_widget_get_direction (widget), + 0); + + return icon_paintable && gtk_icon_paintable_is_symbolic (icon_paintable); +} + +static void +add_provider_to_list (GisGoaPage *page, GVariant *provider) +{ + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + g_autoptr(GIcon) provider_icon = NULL; + g_autofree char *provider_name = NULL; + g_autofree char *provider_type = NULL; + g_autoptr(GVariant) icon_variant = NULL; + ProviderWidget *provider_widget; + GtkWidget *row; + GtkWidget *image; + GtkWidget *arrow_icon; + + g_variant_get (provider, "(ssviu)", + &provider_type, + &provider_name, + &icon_variant, + NULL, + NULL); + + row = adw_action_row_new (); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE); + adw_preferences_row_set_use_markup (ADW_PREFERENCES_ROW (row), TRUE); + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), provider_name); + + provider_icon = g_icon_deserialize (icon_variant); + image = gtk_image_new_from_gicon (provider_icon); + adw_action_row_add_prefix (ADW_ACTION_ROW (row), image); + + if (is_gicon_symbolic (GTK_WIDGET (page), provider_icon)) + { + gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_NORMAL); + gtk_widget_add_css_class (image, "symbolic-circular"); + } + else + { + gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE); + gtk_widget_add_css_class (image, "lowres-icon"); + } + + arrow_icon = gtk_image_new_from_icon_name ("go-next-symbolic"); + adw_action_row_add_suffix (ADW_ACTION_ROW (row), arrow_icon); + + provider_widget = g_new0 (ProviderWidget, 1); + provider_widget->page = page; + provider_widget->provider = g_variant_ref (provider); + provider_widget->row = row; + provider_widget->arrow_icon = arrow_icon; + + g_object_set_data_full (G_OBJECT (row), "widget", provider_widget, g_free); + + g_hash_table_insert (priv->providers, g_steal_pointer (&provider_type), provider_widget); + + gtk_list_box_append (GTK_LIST_BOX (priv->accounts_list), row); +} + +static void +populate_provider_list (GisGoaPage *page) +{ + g_autoptr(GVariant) providers_variant = NULL; + g_autoptr(GError) error = NULL; + g_autofree char *listed_providers = NULL; + GVariantIter iter; + GVariant *provider; + g_auto(GStrv) conf_providers = + gis_driver_conf_get_string_list (GIS_PAGE (page)->driver, VENDOR_GOA_GROUP, VENDOR_PROVIDERS_KEY, NULL); + GStrv providers = conf_providers ? conf_providers : + (gchar *[]) { "google", "owncloud", "windows_live", "facebook", NULL }; + + listed_providers = run_goa_helper_sync ("list-providers", NULL); + providers_variant = g_variant_parse (G_VARIANT_TYPE ("a(ssviu)"), + listed_providers, + NULL, + NULL, + &error); + + if (error) + { + g_warning ("Error listing providers: %s", error->message); + gtk_widget_hide (GTK_WIDGET (page)); + return; + } + + /* This code will read the keyfile containing vendor customization options and + * look for options under the "goa" group, and supports the following keys: + * - providers (optional): list of online account providers to offer + * + * This is how this file might look on a vendor image: + * + * [goa] + * providers=owncloud;imap_smtp + */ + + g_variant_iter_init (&iter, providers_variant); + + while ((provider = g_variant_iter_next_value (&iter))) + { + g_autofree gchar *id = NULL; + + g_variant_get (provider, "(ssviu)", &id, NULL, NULL, NULL, NULL); + + if (g_strv_contains ((const char * const *)providers, id)) + add_provider_to_list (page, provider); + } +} + +static void +sync_visibility (GisGoaPage *page) +{ + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + GisAssistant *assistant = gis_driver_get_assistant (GIS_PAGE (page)->driver); + GNetworkMonitor *network_monitor = g_network_monitor_get_default (); + gboolean visible; + + if (gis_assistant_get_current_page (assistant) == GIS_PAGE (page)) + return; + + visible = (priv->accounts_exist || g_network_monitor_get_network_available (network_monitor)); + gtk_widget_set_visible (GTK_WIDGET (page), visible); +} + +static void +sync_accounts (GisGoaPage *page) +{ + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + GList *accounts, *l; + + accounts = goa_client_get_accounts (priv->goa_client); + + for (l = accounts; l != NULL; l = l->next) { + GoaObject *object = GOA_OBJECT (l->data); + GoaAccount *account = goa_object_get_account (object); + const char *account_type = goa_account_get_provider_type (account); + ProviderWidget *provider_widget; + + provider_widget = g_hash_table_lookup (priv->providers, account_type); + if (!provider_widget) + continue; + + priv->accounts_exist = TRUE; + + if (provider_widget->displayed_account) + continue; + + provider_widget->displayed_account = account; + sync_provider_widget (provider_widget); + } + + g_list_free_full (accounts, (GDestroyNotify) g_object_unref); + + sync_visibility (page); + gis_page_set_skippable (GIS_PAGE (page), !priv->accounts_exist); + gis_page_set_complete (GIS_PAGE (page), priv->accounts_exist); +} + +static void +accounts_changed (GoaClient *client, GoaObject *object, gpointer user_data) +{ + GisGoaPage *page = GIS_GOA_PAGE (user_data); + sync_accounts (page); +} + +static void +network_status_changed (GNetworkMonitor *monitor, + gboolean available, + gpointer user_data) +{ + GisGoaPage *page = GIS_GOA_PAGE (user_data); + sync_visibility (page); +} + +static void +row_activated (GtkListBox *box, + GtkListBoxRow *row, + GisGoaPage *page) +{ + ProviderWidget *provider_widget; + + if (row == NULL) + return; + + provider_widget = g_object_get_data (G_OBJECT (row), "widget"); + g_assert (provider_widget != NULL); + g_assert (provider_widget->displayed_account == NULL); + add_account_to_provider (provider_widget); +} + +#ifdef GDK_WINDOWING_WAYLAND +static void +wayland_window_exported_cb (GdkToplevel *toplevel, + const char *handle, + gpointer data) + +{ + GisGoaPage *page = data; + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + + priv->window_export_handle = g_strdup_printf ("wayland:%s", handle); +} +#endif + +static void +export_window_handle (GisGoaPage *page) +{ + GtkNative *native = gtk_widget_get_native (GTK_WIDGET (page)); + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (native)))) + { + GdkSurface *surface = gtk_native_get_surface (native); + guint32 xid = (guint32) gdk_x11_surface_get_xid (surface); + + priv->window_export_handle = g_strdup_printf ("x11:%x", xid); + } +#endif +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (native)))) + { + GdkSurface *surface = gtk_native_get_surface (native); + + gdk_wayland_toplevel_export_handle (GDK_TOPLEVEL (surface), + wayland_window_exported_cb, + page, + NULL); + } +#endif +} + +static void +unexport_window_handle (GisGoaPage *page) +{ + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + + if (!priv->window_export_handle) + return; + +#ifdef GDK_WINDOWING_WAYLAND + GtkNative *native = gtk_widget_get_native (GTK_WIDGET (page)); + + if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (native)))) + { + GdkSurface *surface = gtk_native_get_surface (native); + gdk_wayland_toplevel_unexport_handle (GDK_TOPLEVEL (surface)); + } +#endif +} + + +static void +gis_goa_page_realize (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (gis_goa_page_parent_class)->realize (widget); + + export_window_handle (GIS_GOA_PAGE (widget)); +} + +static void +gis_goa_page_unrealize (GtkWidget *widget) +{ + unexport_window_handle (GIS_GOA_PAGE (widget)); + + GTK_WIDGET_CLASS (gis_goa_page_parent_class)->unrealize (widget); +} + + +static void +gis_goa_page_constructed (GObject *object) +{ + GisGoaPage *page = GIS_GOA_PAGE (object); + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + GError *error = NULL; + GNetworkMonitor *network_monitor = g_network_monitor_get_default (); + + G_OBJECT_CLASS (gis_goa_page_parent_class)->constructed (object); + + gis_page_set_skippable (GIS_PAGE (page), TRUE); + + priv->providers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + priv->goa_client = goa_client_new_sync (NULL, &error); + + if (priv->goa_client == NULL) { + g_warning ("Failed to get a GoaClient: %s", error->message); + g_error_free (error); + return; + } + + g_signal_connect (priv->goa_client, "account-added", + G_CALLBACK (accounts_changed), page); + g_signal_connect (priv->goa_client, "account-removed", + G_CALLBACK (accounts_changed), page); + g_signal_connect (network_monitor, "network-changed", + G_CALLBACK (network_status_changed), page); + + g_signal_connect (priv->accounts_list, "row-activated", + G_CALLBACK (row_activated), page); + + populate_provider_list (page); + sync_accounts (page); +} + +static void +gis_goa_page_dispose (GObject *object) +{ + GisGoaPage *page = GIS_GOA_PAGE (object); + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + GNetworkMonitor *network_monitor = g_network_monitor_get_default (); + + g_clear_object (&priv->goa_client); + + g_signal_handlers_disconnect_by_func (network_monitor, G_CALLBACK (network_status_changed), page); + + G_OBJECT_CLASS (gis_goa_page_parent_class)->dispose (object); +} + +static void +gis_goa_page_locale_changed (GisPage *page) +{ + gis_page_set_title (GIS_PAGE (page), _("Online Accounts")); +} + +static void +gis_goa_page_class_init (GisGoaPageClass *klass) +{ + GisPageClass *page_class = GIS_PAGE_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/initial-setup/gis-goa-page.ui"); + + gtk_widget_class_bind_template_child_private (widget_class, GisGoaPage, accounts_list); + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_goa_page_locale_changed; + object_class->constructed = gis_goa_page_constructed; + object_class->dispose = gis_goa_page_dispose; + widget_class->realize = gis_goa_page_realize; + widget_class->unrealize = gis_goa_page_unrealize; +} + +static void +gis_goa_page_init (GisGoaPage *page) +{ + g_autoptr(GtkCssProvider) provider = NULL; + + g_resources_register (goa_get_resource ()); + g_type_ensure (GIS_TYPE_PAGE_HEADER); + + gtk_widget_init_template (GTK_WIDGET (page)); + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/initial-setup/gis-goa-page.css"); + gtk_style_context_add_provider_for_display (gdk_display_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} + +GisPage * +gis_prepare_goa_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_GOA_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/goa/gis-goa-page.css b/gnome-initial-setup/pages/goa/gis-goa-page.css new file mode 100644 index 0000000..08a60b6 --- /dev/null +++ b/gnome-initial-setup/pages/goa/gis-goa-page.css @@ -0,0 +1,6 @@ +image.symbolic-circular { + background-color: alpha(currentColor, 0.08); + min-width: 32px; + min-height: 32px; + border-radius: 50%; +} diff --git a/gnome-initial-setup/pages/goa/gis-goa-page.h b/gnome-initial-setup/pages/goa/gis-goa-page.h new file mode 100644 index 0000000..31918bf --- /dev/null +++ b/gnome-initial-setup/pages/goa/gis-goa-page.h @@ -0,0 +1,55 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2012 Red Hat + * + * 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 <http://www.gnu.org/licenses/>. + * + * Written by: + * Jasper St. Pierre <jstpierre@mecheye.net> + */ + +#ifndef __GIS_GOA_PAGE_H__ +#define __GIS_GOA_PAGE_H__ + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_GOA_PAGE (gis_goa_page_get_type ()) +#define GIS_GOA_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_GOA_PAGE, GisGoaPage)) +#define GIS_GOA_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_GOA_PAGE, GisGoaPageClass)) +#define GIS_IS_GOA_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_GOA_PAGE)) +#define GIS_IS_GOA_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_GOA_PAGE)) +#define GIS_GOA_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_GOA_PAGE, GisGoaPageClass)) + +typedef struct _GisGoaPage GisGoaPage; +typedef struct _GisGoaPageClass GisGoaPageClass; + +struct _GisGoaPage +{ + GisPage parent; +}; + +struct _GisGoaPageClass +{ + GisPageClass parent_class; +}; + +GType gis_goa_page_get_type (void); + +GisPage *gis_prepare_goa_page (GisDriver *driver); + +G_END_DECLS + +#endif /* __GIS_GOA_PAGE_H__ */ diff --git a/gnome-initial-setup/pages/goa/gis-goa-page.ui b/gnome-initial-setup/pages/goa/gis-goa-page.ui new file mode 100644 index 0000000..96dab4e --- /dev/null +++ b/gnome-initial-setup/pages/goa/gis-goa-page.ui @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="GisGoaPage" parent="GisPage"> + <child> + <object class="AdwPreferencesPage"> + + <child> + <object class="AdwPreferencesGroup"> + <child> + <object class="GisPageHeader" id="header"> + <property name="margin_top">24</property> + <property name="title" translatable="yes">Connect Your Online Accounts</property> + <property name="subtitle" translatable="yes">Connect your accounts to easily access your email, online calendar, contacts, documents and photos.</property> + <property name="icon_name">goa-panel-symbolic</property> + <property name="show_icon" bind-source="GisGoaPage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/> + </object> + </child> + </object> + </child> + + <child> + <object class="AdwPreferencesGroup"> + <child> + <object class="GtkListBox" id="accounts_list"> + <property name="selection_mode">none</property> + <style> + <class name="boxed-list" /> + </style> + </object> + </child> + </object> + </child> + + <child> + <object class="AdwPreferencesGroup"> + <child> + <object class="GtkLabel" id="footer_label"> + <property name="valign">end</property> + <property name="vexpand">True</property> + <property name="label" translatable="yes">Accounts can be added and removed at any time from the Settings application.</property> + <property name="justify">center</property> + <property name="wrap">True</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + </object> + </child> + + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/goa/gnome-initial-setup-goa-helper.c b/gnome-initial-setup/pages/goa/gnome-initial-setup-goa-helper.c new file mode 100644 index 0000000..5af33db --- /dev/null +++ b/gnome-initial-setup/pages/goa/gnome-initial-setup-goa-helper.c @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2022 Endless OS Foundation, LLC + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: + * Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + */ + +#include "config.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#define GOA_API_IS_SUBJECT_TO_CHANGE +#define GOA_BACKEND_API_IS_SUBJECT_TO_CHANGE +#include <goabackend/goabackend.h> + +#ifdef HAVE_GTK_X11 +#include <gdk/gdkx.h> +#endif +#ifdef HAVE_GTK_WAYLAND +#include <gdk/gdkwayland.h> +#endif + +static GdkDisplay * +get_wayland_display (void) +{ + static GdkDisplay *wayland_display = NULL; + + if (wayland_display) + return wayland_display; + + gdk_set_allowed_backends ("wayland"); + wayland_display = gdk_display_open (NULL); + gdk_set_allowed_backends (NULL); + if (!wayland_display) + g_warning ("Failed to open Wayland display"); + + return wayland_display; +} + +static GdkDisplay * +get_x11_display (void) +{ + static GdkDisplay *x11_display = NULL; + + if (x11_display) + return x11_display; + + gdk_set_allowed_backends ("x11"); + x11_display = gdk_display_open (NULL); + gdk_set_allowed_backends (NULL); + if (!x11_display) + g_warning ("Failed to open X11 display"); + + return x11_display; +} + +static void +set_external_parent_from_handle (GtkApplication *application, + GtkWindow *dialog, + const char *handle_str) +{ + GdkDisplay *display; + GtkWindow *fake_parent; + GdkScreen *screen; + +#ifdef HAVE_GTK_X11 + { + const char *x11_prefix = "x11:"; + if (g_str_has_prefix (handle_str, x11_prefix)) + { + display = get_x11_display (); + if (!display) + { + g_warning ("No X display connection, ignoring X11 parent"); + return; + } + } + } +#endif +#ifdef HAVE_GTK_WAYLAND + { + const char *wayland_prefix = "wayland:"; + + if (g_str_has_prefix (handle_str, wayland_prefix)) + { + display = get_wayland_display (); + if (!display) + { + g_warning ("No Wayland display connection, ignoring Wayland parent"); + return; + } + } + } +#endif + + screen = gdk_display_get_default_screen (gdk_display_get_default ()); + fake_parent = g_object_new (GTK_TYPE_APPLICATION_WINDOW, + "application", application, + "type", GTK_WINDOW_TOPLEVEL, + "screen", screen, + NULL); + g_object_ref_sink (fake_parent); + + gtk_window_set_transient_for (dialog, GTK_WINDOW (fake_parent)); + gtk_window_set_modal (dialog, TRUE); + gtk_widget_realize (GTK_WIDGET (dialog)); + +#ifdef HAVE_GTK_X11 + { + const char *x11_prefix = "x11:"; + if (g_str_has_prefix (handle_str, x11_prefix)) + { + GdkWindow *foreign_gdk_window; + int xid; + + errno = 0; + xid = strtol (handle_str + strlen (x11_prefix), NULL, 16); + if (errno != 0) + { + g_warning ("Failed to reference external X11 window, invalid XID %s", handle_str); + return; + } + + foreign_gdk_window = gdk_x11_window_foreign_new_for_display (display, xid); + if (!foreign_gdk_window) + { + g_warning ("Failed to create foreign window for XID %d", xid); + return; + } + + gdk_window_set_transient_for (gtk_widget_get_window (GTK_WIDGET (dialog)), + foreign_gdk_window); + } + } +#endif +#ifdef HAVE_GTK_WAYLAND + { + const char *wayland_prefix = "wayland:"; + + if (g_str_has_prefix (handle_str, wayland_prefix)) + { + const char *wayland_handle_str = handle_str + strlen (wayland_prefix); + + if (!gdk_wayland_window_set_transient_for_exported (gtk_widget_get_window (GTK_WIDGET (dialog)), + (char *) wayland_handle_str)) + { + g_warning ("Failed to set window transient for external parent"); + return; + } + } + } +#endif + + gtk_window_present (dialog); +} + +/* create-account */ + +static void +on_application_activate_create_account_cb (GtkApplication *application, + char **argv) +{ + g_autoptr(GoaProvider) provider = NULL; + g_autoptr(GoaClient) client = NULL; + g_autoptr(GError) error = NULL; + GoaAccount *account; + GtkWidget *content_area; + GtkWidget *dialog; + GoaObject *object; + + client = goa_client_new_sync (NULL, &error); + if (error) + { + g_printerr ("Error retrieving online accounts client"); + exit (EXIT_FAILURE); + return; + } + + + /* Find the provider with a matching type */ + provider = goa_provider_get_for_provider_type (argv[2]); + if (!provider) + { + g_printerr ("Provider type not supported"); + exit (EXIT_FAILURE); + return; + } + + dialog = g_object_new (GTK_TYPE_DIALOG, + "use-header-bar", 1, + "default-width", 500, + "default-height", 350, + NULL); + g_signal_connect_swapped (dialog, "response", G_CALLBACK (g_application_quit), application); + set_external_parent_from_handle (application, GTK_WINDOW (dialog), argv[3]); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_container_set_border_width (GTK_CONTAINER (content_area), 0); + + object = goa_provider_add_account (provider, + client, + GTK_DIALOG (dialog), + GTK_BOX (content_area), + &error); + if (error) + { + g_printerr ("Failed to create account: %s", error->message); + exit (EXIT_FAILURE); + return; + } + + account = goa_object_peek_account (object); + g_print ("%s", goa_account_get_id (account)); +} + +static int +create_account (int argc, + char **argv) +{ + g_autoptr(GtkApplication) application = NULL; + + gtk_init (&argc, &argv); + + if (argc != 4) + { + g_printerr ("Not enough arguments"); + return EXIT_FAILURE; + } + + application = gtk_application_new ("org.gnome.Settings.GoaHelper", + G_APPLICATION_FLAGS_NONE); + g_signal_connect (application, "activate", G_CALLBACK (on_application_activate_create_account_cb), argv); + + return g_application_run (G_APPLICATION (application), 0, NULL); +} + +/* list-providers */ + +typedef struct { + GMainLoop *mainloop; + GList *providers; + GError *error; +} GetAllProvidersData; + +static void +get_all_providers_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + g_autolist(GoaProvider) providers = NULL; + GetAllProvidersData *data; + + data = user_data; + + goa_provider_get_all_finish (&providers, res, &data->error); + if (data->error) + goto out; + + data->providers = g_steal_pointer (&providers); + +out: + g_main_loop_quit (data->mainloop); +} + +static GList * +get_all_providers (GError **error) +{ + GetAllProvidersData data = (GetAllProvidersData) { + .mainloop = g_main_loop_new (NULL, FALSE), + .providers = NULL, + .error = NULL, + }; + + goa_provider_get_all (get_all_providers_cb, &data); + + g_main_loop_run (data.mainloop); + + if (data.error) + g_propagate_error (error, data.error); + + return data.providers; +} + +static int +list_providers (int argc, + char **argv) +{ + g_autofree char *serialized_result = NULL; + g_autolist(GoaProvider) providers = NULL; + g_autoptr(GVariant) result = NULL; + g_autoptr(GError) error = NULL; + GVariantBuilder b; + GList *l; + + providers = get_all_providers (&error); + + if (error) + { + g_printerr ("%s", error->message); + return EXIT_FAILURE; + } + + g_variant_builder_init (&b, G_VARIANT_TYPE ("a(ssviu)")); + for (l = providers; l; l = l->next) + { + GoaProvider *provider = l->data; + g_autofree char *name = NULL; + g_autoptr(GVariant) icon_variant = NULL; + g_autoptr(GIcon) icon = NULL; + + name = goa_provider_get_provider_name (provider, NULL); + icon = goa_provider_get_provider_icon (provider, NULL); + icon_variant = g_icon_serialize (icon); + + g_variant_builder_add (&b, "(ssviu)", + goa_provider_get_provider_type (provider), + name, + icon_variant, + goa_provider_get_provider_features (provider), + goa_provider_get_credentials_generation (provider)); + } + result = g_variant_builder_end (&b); + + serialized_result = g_variant_print (result, TRUE); + g_print ("%s", serialized_result); + + return EXIT_SUCCESS; +} + +/* show-account */ + +static void +on_remove_button_clicked_cb (GtkButton *button, + GApplication *application) +{ + g_print ("remove"); + g_application_quit (application); +} + +static void +on_application_activate_show_account_cb (GtkApplication *application, + char **argv) +{ + g_autoptr(GoaProvider) provider = NULL; + g_autoptr(GoaObject) object = NULL; + g_autoptr(GoaClient) client = NULL; + g_autoptr(GError) error = NULL; + g_autofree char *title = NULL; + GoaAccount *account; + GtkWidget *content_area; + GtkWidget *button; + GtkWidget *dialog; + GtkWidget *box; + const char *provider_type; + + client = goa_client_new_sync (NULL, &error); + if (error) + { + g_printerr ("Error retrieving online accounts client"); + exit (EXIT_FAILURE); + return; + } + + object = goa_client_lookup_by_id (client, argv[2]); + if (!object) + { + g_printerr ("Online account does not exist"); + exit (EXIT_FAILURE); + return; + } + + /* Find the provider with a matching type */ + account = goa_object_get_account (object); + provider_type = goa_account_get_provider_type (account); + provider = goa_provider_get_for_provider_type (provider_type); + if (!provider) + { + g_printerr ("Provider type not supported"); + exit (EXIT_FAILURE); + return; + } + + dialog = g_object_new (GTK_TYPE_DIALOG, + "use-header-bar", 1, + NULL); + /* Keep account alive so that the switches are still bound to it */ + g_object_set_data_full (G_OBJECT (dialog), "goa-account", account, g_object_unref); + g_signal_connect_swapped (dialog, "response", G_CALLBACK (g_application_quit), application); + set_external_parent_from_handle (application, GTK_WINDOW (dialog), argv[3]); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 42); + gtk_widget_set_margin_bottom (box, 24); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_container_set_border_width (GTK_CONTAINER (content_area), 0); + gtk_container_add (GTK_CONTAINER (content_area), box); + + goa_provider_show_account (provider, + client, + object, + GTK_BOX (box), + NULL, + NULL); + + /* + * The above call doesn't set any widgets to visible, so we have to do that. + * https://gitlab.gnome.org/GNOME/gnome-online-accounts/issues/56 + */ + gtk_widget_show_all (box); + + /* translators: This is the title of the "Show Account" dialog. The + * %s is the name of the provider. e.g., 'Google'. */ + title = g_strdup_printf (_("%s Account"), goa_account_get_provider_name (account)); + gtk_window_set_title (GTK_WINDOW (dialog), title); + + button = gtk_button_new_with_label (_("Remove Account")); + gtk_widget_set_margin_start (box, 24); + gtk_widget_set_margin_end (box, 24); + gtk_widget_set_halign (button, GTK_ALIGN_END); + gtk_widget_set_valign (button, GTK_ALIGN_END); + gtk_widget_set_visible (button, !goa_account_get_is_locked (account)); + gtk_style_context_add_class (gtk_widget_get_style_context (button), "destructive-action"); + gtk_container_add (GTK_CONTAINER (box), button); + g_signal_connect (button, "clicked", G_CALLBACK (on_remove_button_clicked_cb), application); +} + +static int +show_account (int argc, + char **argv) +{ + g_autoptr(GtkApplication) application = NULL; + + gtk_init (&argc, &argv); + + if (argc != 4) + { + g_printerr ("Not enough arguments"); + return EXIT_FAILURE; + } + + application = gtk_application_new ("org.gnome.Settings.GoaHelper", + G_APPLICATION_FLAGS_NONE); + g_signal_connect (application, "activate", G_CALLBACK (on_application_activate_show_account_cb), argv); + + return g_application_run (G_APPLICATION (application), 0, NULL); +} + +struct { + const char *command_name; + int (*command_func) (int argc, + char **argv); +} commands[] = { + { "create-account", create_account, }, + { "list-providers", list_providers, }, + { "show-account", show_account, }, +}; + + +static void +log_handler (const gchar *domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + g_printerr ("%s: %s\n", domain, message); +} + +int +main (int argc, + char **argv) +{ + gsize i; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + if (argc < 2) + return EXIT_FAILURE; + + g_log_set_default_handler (log_handler, NULL); + + for (i = 0; i < G_N_ELEMENTS (commands); i++) + { + if (g_strcmp0 (commands[i].command_name, argv[1]) == 0) + return commands[i].command_func (argc, argv); + } + + return EXIT_SUCCESS; +} diff --git a/gnome-initial-setup/pages/goa/goa.gresource.xml b/gnome-initial-setup/pages/goa/goa.gresource.xml new file mode 100644 index 0000000..045858c --- /dev/null +++ b/gnome-initial-setup/pages/goa/goa.gresource.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks" alias="gis-goa-page.ui">gis-goa-page.ui</file> + <file alias="gis-goa-page.css">gis-goa-page.css</file> + </gresource> +</gresources> + diff --git a/gnome-initial-setup/pages/goa/meson.build b/gnome-initial-setup/pages/goa/meson.build new file mode 100644 index 0000000..b19fcc3 --- /dev/null +++ b/gnome-initial-setup/pages/goa/meson.build @@ -0,0 +1,38 @@ +sources += gnome.compile_resources( + 'goa-resources', + files('goa.gresource.xml'), + c_name: 'goa' +) + +sources += files( + 'gis-goa-page.c', + 'gis-goa-page.h' +) + +goa_helper_deps = [ + dependency('goa-backend-1.0'), +] + +goa_helper_cflags = [] + +gtk_x11_dep = dependency('gtk+-x11-3.0', required: false) +if gtk_x11_dep.found() + goa_helper_deps += [ gtk_x11_dep ] + goa_helper_cflags += ['-DHAVE_GTK_X11'] +endif + +gtk_wayland_dep = dependency('gtk+-wayland-3.0', required: false) +if gtk_wayland_dep.found() + goa_helper_deps += [ gtk_wayland_dep ] + goa_helper_cflags += ['-DHAVE_GTK_WAYLAND'] +endif + +executable( + 'gnome-initial-setup-goa-helper', + 'gnome-initial-setup-goa-helper.c', + include_directories: config_h_dir, + dependencies: goa_helper_deps, + c_args: goa_helper_cflags, + install: true, + install_dir: libexec_dir, +)
\ No newline at end of file |