summaryrefslogtreecommitdiffstats
path: root/gnome-initial-setup/pages/goa
diff options
context:
space:
mode:
Diffstat (limited to 'gnome-initial-setup/pages/goa')
-rw-r--r--gnome-initial-setup/pages/goa/gis-goa-page.c606
-rw-r--r--gnome-initial-setup/pages/goa/gis-goa-page.css6
-rw-r--r--gnome-initial-setup/pages/goa/gis-goa-page.h55
-rw-r--r--gnome-initial-setup/pages/goa/gis-goa-page.ui54
-rw-r--r--gnome-initial-setup/pages/goa/gnome-initial-setup-goa-helper.c505
-rw-r--r--gnome-initial-setup/pages/goa/goa.gresource.xml8
-rw-r--r--gnome-initial-setup/pages/goa/meson.build38
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