/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2011 - 2017 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see .
*
* Author: David Zeuthen
*/
#include "config.h"
#include
#include
#include
#define GOA_API_IS_SUBJECT_TO_CHANGE
#include
#include "cc-online-accounts-panel.h"
#include "cc-online-account-provider-row.h"
#include "cc-online-account-row.h"
#include "cc-online-accounts-resources.h"
#define GOA_API_IS_SUBJECT_TO_CHANGE
#define GOA_BACKEND_API_IS_SUBJECT_TO_CHANGE
#include
struct _CcOnlineAccountsPanel
{
CcPanel parent_instance;
GtkFrame *accounts_frame;
GtkListBox *accounts_listbox;
AdwBanner *offline_banner;
GtkListBox *providers_listbox;
GoaClient *client;
GVariant *parameters;
GListStore *providers;
};
CC_PANEL_REGISTER (CcOnlineAccountsPanel, cc_online_accounts_panel);
enum {
PROP_0,
PROP_PARAMETERS
};
/* Rows methods */
typedef void (*RowForAccountCallback) (CcOnlineAccountsPanel *self, GtkWidget *row, GList *other_rows);
static void
remove_row_for_account_cb (CcOnlineAccountsPanel *self,
GtkWidget *row,
GList *other_rows)
{
gtk_list_box_remove (self->accounts_listbox, row);
gtk_widget_set_visible (GTK_WIDGET (self->accounts_frame), other_rows != NULL);
}
static void
modify_row_for_account (CcOnlineAccountsPanel *self,
GoaObject *object,
RowForAccountCallback callback)
{
GtkWidget *child;
GList *children = NULL;
GList *l;
for (child = gtk_widget_get_first_child (GTK_WIDGET (self->accounts_listbox));
child;
child = gtk_widget_get_next_sibling (child))
{
children = g_list_prepend (children, child);
}
children = g_list_reverse (children);
for (l = children; l != NULL; l = l->next)
{
GoaObject *row_object;
row_object = cc_online_account_row_get_object (CC_ONLINE_ACCOUNT_ROW (l->data));
if (row_object == object)
{
GtkWidget *row = GTK_WIDGET (l->data);
children = g_list_remove_link (children, l);
callback (self, row, children);
g_list_free (l);
break;
}
}
g_list_free (children);
}
static void
show_account_cb (GoaProvider *provider,
GAsyncResult *result,
CcOnlineAccountsPanel *self)
{
g_autoptr (GError) error = NULL;
if (!goa_provider_show_account_finish (provider, result, &error))
{
if (!g_error_matches (error, GOA_ERROR, GOA_ERROR_DIALOG_DISMISSED) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Error showing account: %s", error->message);
}
}
static void
show_account (CcOnlineAccountsPanel *self,
GoaObject *object)
{
g_autoptr (GoaProvider) provider = NULL;
GtkRoot *root;
GoaAccount *account;
const char *provider_type;
/* Find the provider with a matching type */
account = goa_object_peek_account (object);
provider_type = goa_account_get_provider_type (account);
provider = goa_provider_get_for_provider_type (provider_type);
if (provider == NULL)
{
g_warning ("Error showing account: Unsupported provider");
return;
}
root = gtk_widget_get_root (GTK_WIDGET (self));
goa_provider_show_account (provider,
self->client,
object,
GTK_WINDOW (root),
cc_panel_get_cancellable (CC_PANEL (self)),
(GAsyncReadyCallback) show_account_cb,
self);
}
static void
create_account_cb (GoaProvider *provider,
GAsyncResult *result,
CcOnlineAccountsPanel *self)
{
g_autoptr (GoaObject) object = NULL;
g_autoptr (GError) error = NULL;
object = goa_provider_add_account_finish (provider, result, &error);
if (error != NULL)
{
if (!g_error_matches (error, GOA_ERROR, GOA_ERROR_DIALOG_DISMISSED) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Error creating account: %s", error->message);
return;
}
show_account (self, object);
}
static void
create_account (CcOnlineAccountsPanel *self,
GoaProvider *provider)
{
GtkRoot *parent;
g_return_if_fail (GOA_IS_PROVIDER (provider));
parent = gtk_widget_get_root (GTK_WIDGET (self));
goa_provider_add_account (provider,
self->client,
GTK_WINDOW (parent),
cc_panel_get_cancellable (CC_PANEL (self)),
(GAsyncReadyCallback) create_account_cb,
self);
}
static void
select_account_by_id (CcOnlineAccountsPanel *self,
const gchar *account_id)
{
GtkWidget *child;
for (child = gtk_widget_get_first_child (GTK_WIDGET (self->accounts_listbox));
child;
child = gtk_widget_get_next_sibling (child))
{
GoaAccount *account;
GoaObject *row_object;
row_object = cc_online_account_row_get_object (CC_ONLINE_ACCOUNT_ROW (child));
account = goa_object_peek_account (row_object);
if (g_strcmp0 (goa_account_get_id (account), account_id) == 0)
{
show_account (self, row_object);
break;
}
}
}
static void
command_add (CcOnlineAccountsPanel *self,
GVariant *parameters)
{
const gchar *provider_name = NULL;
g_autoptr (GVariant) v = NULL;
g_assert (self != NULL);
g_assert (parameters != NULL);
switch (g_variant_n_children (parameters))
{
case 2:
g_variant_get_child (parameters, 1, "v", &v);
if (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING))
provider_name = g_variant_get_string (v, NULL);
else
g_warning ("Wrong type for the second argument (provider name) GVariant, expected 's' but got '%s'",
(gchar *)g_variant_get_type (v));
break;
default:
g_warning ("Unexpected parameters found, ignore request");
return;
}
if (provider_name != NULL)
{
g_autoptr (GoaProvider) provider = NULL;
unsigned int n_items = 0;
n_items = g_list_model_get_n_items (G_LIST_MODEL (self->providers));
for (unsigned int i = 0; i < n_items; i++)
{
const char *provider_type = NULL;
provider = g_list_model_get_item (G_LIST_MODEL (self->providers), i);
provider_type = goa_provider_get_provider_type (provider);
if (g_strcmp0 (provider_type, provider_name) == 0)
break;
g_clear_object (&provider);
}
if (provider == NULL)
{
g_warning ("Unable to get a provider for type '%s'", provider_name);
return;
}
create_account (self, provider);
}
}
static void
load_custom_css (void)
{
g_autoptr (GtkCssProvider) provider = NULL;
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/online-accounts/online-accounts.css");
gtk_style_context_add_provider_for_display (gdk_display_get_default (),
GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
/* Callbacks */
static int
goa_provider_priority (const char *provider_type)
{
static const char *goa_priority[] = {
"owncloud", /* Nextcloud */
"google", /* Google */
"windows_live", /* Microsoft Personal */
"ms_graph", /* Microsoft 365 */
"exchange", /* Microsoft Exchange */
"fedora", /* Fedora */
"imap_smtp", /* Email (IMAP and SMTP) */
"webdav", /* Calendars, Contacts, Files (WebDAV) */
"kerberos", /* Enterprise Login (Kerberos) */
};
for (size_t i = 0; i < G_N_ELEMENTS (goa_priority); i++)
{
if (g_str_equal (goa_priority[i], provider_type))
return i;
}
/* New or unknown providers are sorted last */
return G_N_ELEMENTS (goa_priority) + 1;
}
static GtkWidget *
provider_create_row (gpointer item,
gpointer user_data)
{
return (GtkWidget *) cc_online_account_provider_row_new (GOA_PROVIDER (item));
}
static int
sort_accounts_func (GtkListBoxRow *a,
GtkListBoxRow *b,
gpointer user_data)
{
GoaAccount *a_account, *b_account;
GoaObject *a_object, *b_object;
const char *a_name, *b_name;
a_object = cc_online_account_row_get_object (CC_ONLINE_ACCOUNT_ROW (a));
a_account = goa_object_peek_account (a_object);
a_name = goa_account_get_provider_type (a_account);
b_object = cc_online_account_row_get_object (CC_ONLINE_ACCOUNT_ROW (b));
b_account = goa_object_peek_account (b_object);
b_name = goa_account_get_provider_type (b_account);
return goa_provider_priority (a_name) - goa_provider_priority (b_name);
}
static int
sort_providers_func (GoaProvider *a,
GoaProvider *b,
gpointer user_data)
{
const char *a_name = goa_provider_get_provider_type (a);
const char *b_name = goa_provider_get_provider_type (b);
return goa_provider_priority (a_name) - goa_provider_priority (b_name);
}
static void
add_account (CcOnlineAccountsPanel *self,
GoaObject *object)
{
CcOnlineAccountRow *row;
row = cc_online_account_row_new (object);
gtk_list_box_append (self->accounts_listbox, GTK_WIDGET (row));
gtk_widget_set_visible (GTK_WIDGET (self->accounts_frame), TRUE);
}
static void
add_provider (CcOnlineAccountsPanel *self,
GoaProvider *provider)
{
g_list_store_insert_sorted (self->providers,
provider,
(GCompareDataFunc) sort_providers_func,
self);
}
static void
on_account_added_cb (CcOnlineAccountsPanel *self,
GoaObject *object)
{
add_account (self, object);
}
static void
on_account_removed_cb (CcOnlineAccountsPanel *self,
GoaObject *object)
{
modify_row_for_account (self, object, remove_row_for_account_cb);
}
static void
on_accounts_listbox_row_activated (CcOnlineAccountsPanel *self,
GtkListBoxRow *activated_row)
{
GoaObject *object = cc_online_account_row_get_object (CC_ONLINE_ACCOUNT_ROW (activated_row));
show_account (self, object);
}
static void
on_provider_row_activated_cb (CcOnlineAccountsPanel *self,
GtkListBoxRow *activated_row)
{
GoaProvider *provider = cc_online_account_provider_row_get_provider (CC_ONLINE_ACCOUNT_PROVIDER_ROW (activated_row));
create_account (self, provider);
}
static void
goa_provider_get_all_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr (CcOnlineAccountsPanel) self = CC_ONLINE_ACCOUNTS_PANEL (user_data);
g_autolist (GoaProvider) providers = NULL;
g_autolist (GoaAccount) accounts = NULL;
g_autoptr (GError) error = NULL;
/* goa_provider_get_all() doesn't have a cancellable argument, so check if
* the panel cancellable was triggered.
*/
if (g_cancellable_is_cancelled (cc_panel_get_cancellable (CC_PANEL (self))))
return;
if (!goa_provider_get_all_finish (&providers, res, &error))
{
g_warning ("Error listing providers: %s", error->message);
return;
}
for (const GList *iter = providers; iter != NULL; iter = iter->next)
add_provider (self, GOA_PROVIDER (iter->data));
/* Load existing accounts */
accounts = goa_client_get_accounts (self->client);
for (const GList *iter = accounts; iter != NULL; iter = iter->next)
add_account (self, GOA_OBJECT (iter->data));
g_signal_connect_swapped (self->client,
"account-added",
G_CALLBACK (on_account_added_cb),
self);
g_signal_connect_swapped (self->client,
"account-removed",
G_CALLBACK (on_account_removed_cb),
self);
/* With the client ready, check if we have a pending command */
gtk_widget_set_sensitive (GTK_WIDGET (self), TRUE);
if (self->parameters != NULL)
{
g_autoptr (GVariant) parameters = NULL;
parameters = g_steal_pointer (&self->parameters);
g_object_set (self, "parameters", parameters, NULL);
}
}
static void
goa_client_new_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr (CcOnlineAccountsPanel) self = CC_ONLINE_ACCOUNTS_PANEL (user_data);
g_autoptr (GError) error = NULL;
self->client = goa_client_new_finish (res, &error);
if (self->client == NULL)
{
g_warning ("Error connect to service: %s", error->message);
gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
return;
}
goa_provider_get_all (goa_provider_get_all_cb, g_object_ref (self));
}
/* CcPanel overrides */
static const char *
cc_online_accounts_panel_get_help_uri (CcPanel *panel)
{
return "help:gnome-help/accounts";
}
/* GObject overrides */
static void
cc_online_accounts_panel_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
CcOnlineAccountsPanel *self = CC_ONLINE_ACCOUNTS_PANEL (object);
switch (property_id)
{
case PROP_PARAMETERS:
{
GVariant *parameters;
g_autoptr (GVariant) v = NULL;
const gchar *first_arg = NULL;
parameters = g_value_get_variant (value);
if (parameters == NULL)
return;
if (g_variant_n_children (parameters) > 0)
{
g_variant_get_child (parameters, 0, "v", &v);
if (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING))
first_arg = g_variant_get_string (v, NULL);
else
g_warning ("Wrong type for the second argument GVariant, expected 's' but got '%s'",
(gchar *)g_variant_get_type (v));
}
/* Waiting for the client to load */
if (self->client == NULL)
{
g_clear_pointer (&self->parameters, g_variant_unref);
self->parameters = g_value_dup_variant (value);
}
else if (g_strcmp0 (first_arg, "add") == 0)
{
command_add (CC_ONLINE_ACCOUNTS_PANEL (object), parameters);
}
else if (first_arg != NULL)
{
select_account_by_id (CC_ONLINE_ACCOUNTS_PANEL (object), first_arg);
}
return;
}
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
cc_online_accounts_panel_finalize (GObject *object)
{
CcOnlineAccountsPanel *self = CC_ONLINE_ACCOUNTS_PANEL (object);
g_clear_object (&self->client);
g_clear_pointer (&self->parameters, g_variant_unref);
g_clear_object (&self->providers);
G_OBJECT_CLASS (cc_online_accounts_panel_parent_class)->finalize (object);
}
static void
cc_online_accounts_panel_class_init (CcOnlineAccountsPanelClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
panel_class->get_help_uri = cc_online_accounts_panel_get_help_uri;
object_class->set_property = cc_online_accounts_panel_set_property;
object_class->finalize = cc_online_accounts_panel_finalize;
g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters");
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/online-accounts/cc-online-accounts-panel.ui");
gtk_widget_class_bind_template_child (widget_class, CcOnlineAccountsPanel, accounts_frame);
gtk_widget_class_bind_template_child (widget_class, CcOnlineAccountsPanel, accounts_listbox);
gtk_widget_class_bind_template_child (widget_class, CcOnlineAccountsPanel, offline_banner);
gtk_widget_class_bind_template_child (widget_class, CcOnlineAccountsPanel, providers_listbox);
gtk_widget_class_bind_template_callback (widget_class, on_accounts_listbox_row_activated);
gtk_widget_class_bind_template_callback (widget_class, on_provider_row_activated_cb);
}
static void
cc_online_accounts_panel_init (CcOnlineAccountsPanel *self)
{
GNetworkMonitor *monitor;
g_resources_register (cc_online_accounts_get_resource ());
gtk_widget_init_template (GTK_WIDGET (self));
gtk_list_box_set_sort_func (self->accounts_listbox,
sort_accounts_func,
self,
NULL);
self->providers = g_list_store_new (GOA_TYPE_PROVIDER);
gtk_list_box_bind_model (self->providers_listbox,
G_LIST_MODEL (self->providers),
provider_create_row,
self,
NULL);
monitor = g_network_monitor_get_default();
g_object_bind_property (monitor,
"network-available",
self->offline_banner,
"revealed",
G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
g_object_bind_property (monitor,
"network-available",
self->providers_listbox,
"sensitive",
G_BINDING_SYNC_CREATE);
load_custom_css ();
/* Disable the panel while we wait for the client */
gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
goa_client_new (cc_panel_get_cancellable (CC_PANEL (self)),
goa_client_new_cb,
g_object_ref (self));
}