[gnome-initial-setup/wip/port-to-gtk4: 104/110] goa: Port to GTK4




commit d2f42c6daca183ac3df2f07d273219024d2f7698
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Thu Jul 7 10:41:03 2022 -0300

    goa: Port to GTK4
    
    This introduces a new helper executable that is identical to the
    one in Settings, that handles the goa-daemon API calls. It also
    cleans up a lot of code by using AdwActionRows.

 gnome-initial-setup/gnome-initial-setup.c          |   4 +-
 gnome-initial-setup/meson.build                    |   3 +-
 gnome-initial-setup/pages/goa/gis-goa-page.c       | 442 +++++++++++++-----
 gnome-initial-setup/pages/goa/gis-goa-page.css     |   6 +
 gnome-initial-setup/pages/goa/gis-goa-page.ui      |  76 ++--
 .../pages/goa/gnome-initial-setup-goa-helper.c     | 505 +++++++++++++++++++++
 gnome-initial-setup/pages/goa/goa.gresource.xml    |   1 +
 gnome-initial-setup/pages/goa/meson.build          |  28 ++
 gnome-initial-setup/pages/meson.build              |   2 +-
 meson.build                                        |   1 +
 10 files changed, 905 insertions(+), 163 deletions(-)
---
diff --git a/gnome-initial-setup/gnome-initial-setup.c b/gnome-initial-setup/gnome-initial-setup.c
index 2f5d3cf6..5a2c21a5 100644
--- a/gnome-initial-setup/gnome-initial-setup.c
+++ b/gnome-initial-setup/gnome-initial-setup.c
@@ -36,7 +36,7 @@
 #include "pages/timezone/gis-timezone-page.h"
 #include "pages/privacy/gis-privacy-page.h"
 #include "pages/software/gis-software-page.h"
-//#include "pages/goa/gis-goa-page.h"
+#include "pages/goa/gis-goa-page.h"
 //#include "pages/account/gis-account-pages.h"
 //#include "pages/parental-controls/gis-parental-controls-page.h"
 //#include "pages/password/gis-password-page.h"
@@ -69,7 +69,7 @@ static PageData page_table[] = {
   PAGE (privacy,  FALSE),
   PAGE (timezone, TRUE),
   PAGE (software, TRUE),
-  //PAGE (goa,      FALSE),
+  PAGE (goa,      FALSE),
   //PAGE (account,  TRUE),
   //PAGE (password, TRUE),
 #ifdef HAVE_PARENTAL_CONTROLS
diff --git a/gnome-initial-setup/meson.build b/gnome-initial-setup/meson.build
index 09403429..cbfb8c73 100644
--- a/gnome-initial-setup/meson.build
+++ b/gnome-initial-setup/meson.build
@@ -43,8 +43,7 @@ dependencies = [
     dependency ('gnome-desktop-4'),
     dependency ('gsettings-desktop-schemas', version: '>= 3.37.1'),
     dependency ('fontconfig'),
-    #dependency ('goa-1.0'),
-    #dependency ('goa-backend-1.0'),
+    dependency ('goa-1.0'),
     dependency ('gtk4', version: '>= 4.6'),
     dependency ('glib-2.0', version: '>= 2.63.1'),
     dependency ('gio-unix-2.0', version: '>= 2.53.0'),
diff --git a/gnome-initial-setup/pages/goa/gis-goa-page.c b/gnome-initial-setup/pages/goa/gis-goa-page.c
index 87341c41..8376196e 100644
--- a/gnome-initial-setup/pages/goa/gis-goa-page.c
+++ b/gnome-initial-setup/pages/goa/gis-goa-page.c
@@ -29,14 +29,19 @@
 
 #define GOA_API_IS_SUBJECT_TO_CHANGE
 #include <goa/goa.h>
-#define GOA_BACKEND_API_IS_SUBJECT_TO_CHANGE
-#include <goabackend/goabackend.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"
 
@@ -46,6 +51,7 @@ struct _GisGoaPagePrivate {
   GoaClient *goa_client;
   GHashTable *providers;
   gboolean accounts_exist;
+  char *window_export_handle;
 };
 typedef struct _GisGoaPagePrivate GisGoaPagePrivate;
 
@@ -53,141 +59,268 @@ G_DEFINE_TYPE_WITH_PRIVATE (GisGoaPage, gis_goa_page, GIS_TYPE_PAGE);
 
 struct _ProviderWidget {
   GisGoaPage *page;
-  GoaProvider *provider;
+  GVariant *provider;
   GoaAccount *displayed_account;
 
   GtkWidget *row;
-  GtkWidget *checkmark;
-  GtkWidget *account_label;
+  GtkWidget *arrow_icon;
 };
 typedef struct _ProviderWidget ProviderWidget;
 
+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->checkmark, has_account);
-  gtk_widget_set_visible (provider_widget->account_label, has_account);
-  gtk_widget_set_sensitive (provider_widget->row, !has_account);
+  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);
 
-  if (has_account) {
-    char *markup;
-    markup = g_strdup_printf ("<small><span foreground=\"#555555\">%s</span></small>",
-                              goa_account_get_presentation_identity (provider_widget->displayed_account));
-    gtk_label_set_markup (GTK_LABEL (provider_widget->account_label), markup);
-    g_free (markup);
-  }
 }
 
 static void
-add_account_to_provider (ProviderWidget *provider_widget)
+on_create_account_finish_cb (GObject      *source_object,
+                             GAsyncResult *result,
+                             gpointer      user_data)
 {
-  GisGoaPage *page = provider_widget->page;
-  GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page);
-  GtkWindow *parent = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page)));
-  GError *error = NULL;
-  GtkWidget *dialog;
-
-  dialog = gtk_dialog_new_with_buttons (_("Add Account"),
-                                        parent,
-                                        GTK_DIALOG_MODAL
-                                        | GTK_DIALOG_DESTROY_WITH_PARENT
-                                        | GTK_DIALOG_USE_HEADER_BAR,
-                                        NULL, NULL);
-
-  goa_provider_add_account (provider_widget->provider,
-                            priv->goa_client,
-                            GTK_DIALOG (dialog),
-                            GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
-                            &error);
-
-  /* this will fire the `account-added` signal, which will do
-   * the syncing of displayed_account on its own */
-
-  if (error) {
-    if (!g_error_matches (error, GOA_ERROR, GOA_ERROR_DIALOG_DISMISSED))
-      g_warning ("fart %s", error->message);
-    goto out;
-  }
+  g_autofree char *new_account_id = NULL;
+  g_autoptr(GError) error = NULL;
+
+  new_account_id = g_task_propagate_pointer (G_TASK (result), &error);
 
- out:
-  gtk_widget_destroy (dialog);
+  if (error)
+    g_warning ("Error showing account: %s", error->message);
 }
 
 static void
-add_provider_to_list (GisGoaPage *page, const char *provider_type)
+add_account_to_provider (ProviderWidget *provider_widget)
 {
+  GisGoaPage *page = provider_widget->page;
   GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page);
-  GtkWidget *row;
-  GtkWidget *box;
-  GtkWidget *image;
-  GtkWidget *label;
-  GtkWidget *checkmark;
-  GtkWidget *account_label;
-  GIcon *icon;
-  gchar *markup, *provider_name;
-  GoaProvider *provider;
-  ProviderWidget *provider_widget;
+  g_autofree char *provider_type = NULL;
 
-  provider = goa_provider_get_for_provider_type (provider_type);
-  if (provider == NULL)
+  if (!priv->window_export_handle)
     return;
 
-  row = gtk_list_box_row_new ();
-  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
-  g_object_set (box, "margin", 4, NULL);
-  gtk_widget_set_hexpand (box, TRUE);
-
-  icon = goa_provider_get_provider_icon (provider, NULL);
-  image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
-  g_object_unref (icon);
-
-  provider_name = goa_provider_get_provider_name (provider, NULL);
-  markup = g_strdup_printf ("<b>%s</b>", provider_name);
-  label = gtk_label_new (NULL);
-  gtk_label_set_markup (GTK_LABEL (label), markup);
-  g_free (markup);
-  g_free (provider_name);
-
-  checkmark = gtk_image_new_from_icon_name ("object-select-symbolic", GTK_ICON_SIZE_MENU);
+  g_variant_get (provider_widget->provider, "(ssviu)", &provider_type, NULL, NULL, NULL, NULL);
 
-  account_label = gtk_label_new (NULL);
-
-  gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
-  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
-  gtk_box_pack_end (GTK_BOX (box), checkmark, FALSE, FALSE, 8);
-  gtk_box_pack_end (GTK_BOX (box), account_label, FALSE, FALSE, 0);
+  run_goa_helper_async ("create-account",
+                        provider_type,
+                        priv->window_export_handle,
+                        NULL,
+                        on_create_account_finish_cb,
+                        page);
+}
 
-  gtk_container_add (GTK_CONTAINER (row), box);
+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);
+}
 
-  gtk_widget_show (label);
-  gtk_widget_show (image);
-  gtk_widget_show (box);
-  gtk_widget_show (row);
+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 = provider;
+  provider_widget->provider = g_variant_ref (provider);
   provider_widget->row = row;
-  provider_widget->checkmark = checkmark;
-  provider_widget->account_label = account_label;
+  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, (char *) provider_type, provider_widget);
+  g_hash_table_insert (priv->providers, g_steal_pointer (&provider_type), provider_widget);
 
-  gtk_container_add (GTK_CONTAINER (priv->accounts_list), row);
+  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
@@ -198,8 +331,17 @@ populate_provider_list (GisGoaPage *page)
    *   providers=owncloud;imap_smtp
    */
 
-  for (guint i = 0; providers[i]; i++)
-    add_provider_to_list (page, providers[i]);
+  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
@@ -267,21 +409,6 @@ network_status_changed (GNetworkMonitor *monitor,
   sync_visibility (page);
 }
 
-static void
-update_header_func (GtkListBoxRow *child,
-                    GtkListBoxRow *before,
-                    gpointer       user_data)
-{
-  GtkWidget *header;
-
-  if (before == NULL)
-    return;
-
-  header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
-  gtk_list_box_row_set_header (child, header);
-  gtk_widget_show (header);
-}
-
 static void
 row_activated (GtkListBox    *box,
                GtkListBoxRow *row,
@@ -298,6 +425,85 @@ row_activated (GtkListBox    *box,
   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)
 {
@@ -310,7 +516,7 @@ gis_goa_page_constructed (GObject *object)
 
   gis_page_set_skippable (GIS_PAGE (page), TRUE);
 
-  priv->providers = g_hash_table_new (g_str_hash, g_str_equal);
+  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);
 
@@ -327,9 +533,6 @@ gis_goa_page_constructed (GObject *object)
   g_signal_connect (network_monitor, "network-changed",
                     G_CALLBACK (network_status_changed), page);
 
-  gtk_list_box_set_header_func (GTK_LIST_BOX (priv->accounts_list),
-                                update_header_func,
-                                NULL, NULL);
   g_signal_connect (priv->accounts_list, "row-activated",
                     G_CALLBACK (row_activated), page);
 
@@ -362,24 +565,35 @@ 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 (GTK_WIDGET_CLASS (klass), 
"/org/gnome/initial-setup/gis-goa-page.ui");
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/initial-setup/gis-goa-page.ui");
 
-  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisGoaPage, accounts_list);
+  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 *
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 00000000..08a60b69
--- /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.ui b/gnome-initial-setup/pages/goa/gis-goa-page.ui
index 9ef1d95f..96dab4e2 100644
--- a/gnome-initial-setup/pages/goa/gis-goa-page.ui
+++ b/gnome-initial-setup/pages/goa/gis-goa-page.ui
@@ -1,65 +1,53 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.16.0 on Wed Oct 23 11:13:34 2013 -->
 <interface>
-  <!-- interface-requires gtk+ 3.0 -->
   <template class="GisGoaPage" parent="GisPage">
     <child>
-      <object class="GtkBox" id="box">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="halign">center</property>
-        <property name="valign">fill</property>
-        <property name="orientation">vertical</property>
+      <object class="AdwPreferencesPage">
+
         <child>
-          <object class="GisPageHeader" id="header">
-            <property name="visible">True</property>
-            <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 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="GtkFrame" id="frame">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="label_xalign">0</property>
-            <property name="shadow_type">in</property>
-            <property name="margin_top">18</property>
+          <object class="AdwPreferencesGroup">
             <child>
               <object class="GtkListBox" id="accounts_list">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="selection_mode">none</property>
+                <style>
+                  <class name="boxed-list" />
+                </style>
               </object>
             </child>
           </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">3</property>
-          </packing>
         </child>
+
         <child>
-          <object class="GtkLabel" id="footer_label">
-            <property name="visible">True</property>
-            <property name="can_focus">False</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>
-            <property name="margin_bottom">18</property>
-            <style>
-              <class name="dim-label"/>
-            </style>
+          <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>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="pack_type">end</property>
-            <property name="position">4</property>
-          </packing>
         </child>
+
       </object>
     </child>
   </template>
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 00000000..1b40c84c
--- /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", 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
index ad1cfdfd..045858c2 100644
--- a/gnome-initial-setup/pages/goa/goa.gresource.xml
+++ b/gnome-initial-setup/pages/goa/goa.gresource.xml
@@ -2,6 +2,7 @@
 <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
index c7dcaaa5..b19fcc34 100644
--- a/gnome-initial-setup/pages/goa/meson.build
+++ b/gnome-initial-setup/pages/goa/meson.build
@@ -8,3 +8,31 @@ 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
diff --git a/gnome-initial-setup/pages/meson.build b/gnome-initial-setup/pages/meson.build
index 13313293..93c1646a 100644
--- a/gnome-initial-setup/pages/meson.build
+++ b/gnome-initial-setup/pages/meson.build
@@ -5,7 +5,7 @@ pages = [
    'network',
    'timezone',
    'privacy',
-   #'goa',
+   'goa',
    #'password',
    'software',
    'summary',
diff --git a/meson.build b/meson.build
index 1f315a81..51406bc4 100644
--- a/meson.build
+++ b/meson.build
@@ -26,6 +26,7 @@ conf.set_quoted('PKGDATADIR', pkgdata_dir)
 conf.set_quoted('DATADIR', data_dir)
 conf.set_quoted('PKGSYSCONFDIR', pkgsysconf_dir)
 conf.set_quoted('SYSCONFDIR', sysconf_dir)
+conf.set_quoted('LIBEXECDIR', libexec_dir)
 conf.set('SECRET_API_SUBJECT_TO_CHANGE', true)
 conf.set_quoted('G_LOG_DOMAIN', 'InitialSetup')
 conf.set('GLIB_VERSION_MIN_REQUIRED', 'GLIB_VERSION_2_64')


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]