/* -*- 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 .
*
* Written by:
* Jasper St. Pierre
*/
/* 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
#include
#include
#include "gis-page-header.h"
#ifdef GDK_WINDOWING_X11
#include
#endif
#ifdef GDK_WINDOWING_WAYLAND
#include
#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);
}