client: accounts: Add support for Thunderbird autoconfig
- Auto detect server settings - Rework accounts editor add panel Fix #1390 Fix #1350
This commit is contained in:
parent
14d5a4f510
commit
a14f5d4799
7 changed files with 507 additions and 363 deletions
|
|
@ -93,6 +93,7 @@ libhandy = dependency('libhandy-1', version: '>= 1.2.1', required: false)
|
|||
libmath = cc.find_library('m')
|
||||
libpeas = dependency('libpeas-1.0', version: '>= 1.24.0')
|
||||
libsecret = dependency('libsecret-1', version: '>= 0.11')
|
||||
libsoup = dependency('libsoup-3.0')
|
||||
libstemmer_dep = cc.find_library('stemmer')
|
||||
libunwind_dep = dependency(
|
||||
'libunwind', version: '>= 1.1', required: get_option('libunwind')
|
||||
|
|
|
|||
170
src/client/accounts/accounts-autoconfig.vala
Normal file
170
src/client/accounts/accounts-autoconfig.vala
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright 2022 Cédric Bellegarde <cedric.bellegarde@adishatz.org>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Thunderbird autoconfig XML values
|
||||
*/
|
||||
internal class Accounts.AutoConfigValues {
|
||||
// emailProvider.id
|
||||
public string id { get; set; default = ""; }
|
||||
|
||||
// incomingServer[type="imap"].hostname
|
||||
public string imap_server { get; set; default = ""; }
|
||||
// incomingServer[type="imap"].port
|
||||
public string imap_port { get; set; default = ""; }
|
||||
// incomingServer[type="imap"].socketType
|
||||
public Geary.TlsNegotiationMethod imap_tls_method {
|
||||
get; set; default = Geary.TlsNegotiationMethod.TRANSPORT;
|
||||
}
|
||||
|
||||
// outgoingServer[type="smtp"].hostname
|
||||
public string smtp_server{ get; set; default = ""; }
|
||||
// outgoingServer[type="smtp"].port
|
||||
public string smtp_port { get; set; default = ""; }
|
||||
// outgoingServer[type="smtp"].socketType
|
||||
public Geary.TlsNegotiationMethod smtp_tls_method {
|
||||
get; set; default = Geary.TlsNegotiationMethod.TRANSPORT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal errordomain Accounts.AutoConfigError {
|
||||
ERROR
|
||||
}
|
||||
|
||||
/**
|
||||
* An account autoconfiguration helper
|
||||
*/
|
||||
internal class Accounts.AutoConfig {
|
||||
|
||||
private static string AUTOCONFIG_BASE_URI = "https://autoconfig.thunderbird.net/v1.1/";
|
||||
private static string AUTOCONFIG_PATH = "/mail/config-v1.1.xml";
|
||||
|
||||
private unowned GLib.Cancellable cancellable;
|
||||
|
||||
internal AutoConfig(GLib.Cancellable auto_config_cancellable) {
|
||||
cancellable = auto_config_cancellable;
|
||||
}
|
||||
|
||||
public async AutoConfigValues get_config(string hostname)
|
||||
throws AutoConfigError {
|
||||
AutoConfigValues auto_config_values;
|
||||
|
||||
// First try to get config from mail domain, then from thunderbird
|
||||
try {
|
||||
auto_config_values = yield get_config_for_uri(
|
||||
"https://autoconfig." + hostname + AUTOCONFIG_PATH
|
||||
);
|
||||
} catch (AutoConfigError err) {
|
||||
auto_config_values = yield get_config_for_uri(
|
||||
AUTOCONFIG_BASE_URI + hostname
|
||||
);
|
||||
}
|
||||
return auto_config_values;
|
||||
}
|
||||
|
||||
private async AutoConfigValues get_config_for_uri(string uri)
|
||||
throws AutoConfigError {
|
||||
GLib.InputStream stream;
|
||||
var session = new Soup.Session();
|
||||
var msg = new Soup.Message("GET", uri);
|
||||
|
||||
try {
|
||||
stream = yield session.send_async(
|
||||
msg, Priority.DEFAULT, this.cancellable
|
||||
);
|
||||
} catch (GLib.Error err) {
|
||||
throw new AutoConfigError.ERROR(err.message);
|
||||
}
|
||||
|
||||
try {
|
||||
var stdout_stream = new MemoryOutputStream.resizable();
|
||||
yield stdout_stream.splice_async(
|
||||
stream, 0, Priority.DEFAULT, null
|
||||
);
|
||||
stdout_stream.write("\0".data);
|
||||
stdout_stream.close();
|
||||
unowned var xml_data = (string) stdout_stream.get_data();
|
||||
return get_config_for_xml(xml_data);
|
||||
} catch (GLib.Error err) {
|
||||
throw new AutoConfigError.ERROR(err.message);
|
||||
} finally {
|
||||
try {
|
||||
yield stream.close_async();
|
||||
} catch (GLib.Error err) {
|
||||
// Oh well
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AutoConfigValues get_config_for_xml(string xml_data)
|
||||
throws AutoConfigError {
|
||||
unowned Xml.Doc doc = Xml.Parser.parse_memory(xml_data, xml_data.length);
|
||||
if (doc == null) {
|
||||
throw new AutoConfigError.ERROR("Invalid XML");
|
||||
}
|
||||
|
||||
unowned Xml.Node root = doc.get_root_element();
|
||||
unowned Xml.Node email_provider = get_node(root, "emailProvider");
|
||||
unowned Xml.Node incoming_server = get_node(email_provider, "incomingServer");
|
||||
unowned Xml.Node outgoing_server = get_node(email_provider, "outgoingServer");
|
||||
|
||||
if (incoming_server == null || outgoing_server == null) {
|
||||
throw new AutoConfigError.ERROR("Invalid XML");
|
||||
}
|
||||
|
||||
if (incoming_server.get_prop("type") != "imap" ||
|
||||
outgoing_server.get_prop("type") != "smtp") {
|
||||
throw new AutoConfigError.ERROR("Unsupported protocol");
|
||||
}
|
||||
|
||||
var auto_config_values = new AutoConfigValues();
|
||||
|
||||
auto_config_values.id = email_provider.get_prop("id");
|
||||
|
||||
auto_config_values.imap_server = get_node_value(incoming_server, "hostname");
|
||||
auto_config_values.imap_port = get_node_value(incoming_server, "port");
|
||||
auto_config_values.imap_tls_method = get_tls_method(
|
||||
get_node_value(incoming_server, "socketType")
|
||||
);
|
||||
|
||||
auto_config_values.smtp_server = get_node_value(outgoing_server, "hostname");
|
||||
auto_config_values.smtp_port = get_node_value(outgoing_server, "port");
|
||||
auto_config_values.smtp_tls_method = get_tls_method(
|
||||
get_node_value(outgoing_server, "socketType")
|
||||
);
|
||||
|
||||
return auto_config_values;
|
||||
}
|
||||
|
||||
private unowned Xml.Node? get_node(Xml.Node root, string name) {
|
||||
for (unowned Xml.Node entry = root.children; entry != null; entry = entry.next) {
|
||||
if (entry.type == Xml.ElementType.ELEMENT_NODE && entry.name == name) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private string get_node_value(Xml.Node root, string name) {
|
||||
unowned Xml.Node? node = get_node(root, name);
|
||||
if (node == null)
|
||||
return "";
|
||||
return node.get_content();
|
||||
}
|
||||
|
||||
private Geary.TlsNegotiationMethod get_tls_method(string method) {
|
||||
switch (method) {
|
||||
case "SSL":
|
||||
return Geary.TlsNegotiationMethod.TRANSPORT;
|
||||
case "STARTTLS":
|
||||
return Geary.TlsNegotiationMethod.START_TLS;
|
||||
default:
|
||||
return Geary.TlsNegotiationMethod.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,31 +37,29 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
|||
|
||||
[GtkChild] private unowned Gtk.HeaderBar header;
|
||||
|
||||
[GtkChild] private unowned Gtk.Grid pane_content;
|
||||
[GtkChild] private unowned Gtk.Stack stack;
|
||||
|
||||
[GtkChild] private unowned Gtk.Adjustment pane_adjustment;
|
||||
|
||||
[GtkChild] private unowned Gtk.ListBox details_list;
|
||||
|
||||
[GtkChild] private unowned Gtk.Grid receiving_panel;
|
||||
|
||||
[GtkChild] private unowned Gtk.ListBox receiving_list;
|
||||
|
||||
[GtkChild] private unowned Gtk.Grid sending_panel;
|
||||
|
||||
[GtkChild] private unowned Gtk.ListBox sending_list;
|
||||
|
||||
[GtkChild] private unowned Gtk.Button create_button;
|
||||
[GtkChild] private unowned Gtk.Button action_button;
|
||||
|
||||
[GtkChild] private unowned Gtk.Button back_button;
|
||||
|
||||
[GtkChild] private unowned Gtk.Spinner create_spinner;
|
||||
[GtkChild] private unowned Gtk.Spinner action_spinner;
|
||||
|
||||
private NameRow real_name;
|
||||
private EmailRow email = new EmailRow();
|
||||
private string last_valid_email = "";
|
||||
private string last_valid_hostname = "";
|
||||
|
||||
private GLib.Cancellable auto_config_cancellable = new GLib.Cancellable();
|
||||
|
||||
private HostnameRow imap_hostname = new HostnameRow(Geary.Protocol.IMAP);
|
||||
private TransportSecurityRow imap_tls = new TransportSecurityRow();
|
||||
private LoginRow imap_login = new LoginRow();
|
||||
|
|
@ -76,32 +74,19 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
|||
private bool controls_valid = false;
|
||||
|
||||
|
||||
internal EditorAddPane(Editor editor, Geary.ServiceProvider provider) {
|
||||
internal EditorAddPane(Editor editor) {
|
||||
this.editor = editor;
|
||||
this.provider = provider;
|
||||
this.provider = Geary.ServiceProvider.OTHER;
|
||||
|
||||
this.accounts = editor.application.controller.account_manager;
|
||||
this.engine = editor.application.engine;
|
||||
|
||||
this.pane_content.set_focus_vadjustment(this.pane_adjustment);
|
||||
this.stack.set_focus_vadjustment(this.pane_adjustment);
|
||||
|
||||
this.details_list.set_header_func(Editor.seperator_headers);
|
||||
this.receiving_list.set_header_func(Editor.seperator_headers);
|
||||
this.sending_list.set_header_func(Editor.seperator_headers);
|
||||
|
||||
if (provider != Geary.ServiceProvider.OTHER) {
|
||||
this.details_list.add(
|
||||
new ServiceProviderRow<EditorAddPane>(
|
||||
provider,
|
||||
// Translators: Label for adding an email account
|
||||
// account for a generic IMAP service provider.
|
||||
_("All others")
|
||||
)
|
||||
);
|
||||
this.receiving_panel.hide();
|
||||
this.sending_panel.hide();
|
||||
}
|
||||
|
||||
this.real_name = new NameRow(this.accounts.get_account_name());
|
||||
|
||||
this.details_list.add(this.real_name);
|
||||
|
|
@ -130,18 +115,14 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
|||
this.smtp_password.validator.state_changed.connect(on_validated);
|
||||
this.smtp_password.value.activate.connect(on_activated);
|
||||
|
||||
if (provider == Geary.ServiceProvider.OTHER) {
|
||||
this.receiving_list.add(this.imap_hostname);
|
||||
this.receiving_list.add(this.imap_tls);
|
||||
this.receiving_list.add(this.imap_login);
|
||||
this.receiving_list.add(this.imap_password);
|
||||
this.receiving_list.add(this.imap_hostname);
|
||||
this.receiving_list.add(this.imap_tls);
|
||||
this.receiving_list.add(this.imap_login);
|
||||
this.receiving_list.add(this.imap_password);
|
||||
|
||||
this.sending_list.add(this.smtp_hostname);
|
||||
this.sending_list.add(this.smtp_tls);
|
||||
this.sending_list.add(this.smtp_auth);
|
||||
} else {
|
||||
this.details_list.add(this.imap_password);
|
||||
}
|
||||
this.sending_list.add(this.smtp_hostname);
|
||||
this.sending_list.add(this.smtp_tls);
|
||||
this.sending_list.add(this.smtp_auth);
|
||||
}
|
||||
|
||||
internal Gtk.HeaderBar get_header() {
|
||||
|
|
@ -169,7 +150,8 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
|||
account.outgoing = new_smtp_service();
|
||||
account.untrusted_host.connect(on_untrusted_host);
|
||||
|
||||
if (this.provider == Geary.ServiceProvider.OTHER) {
|
||||
if (this.provider == Geary.ServiceProvider.OTHER &&
|
||||
this.imap_hostname.get_visible()) {
|
||||
bool imap_valid = false;
|
||||
bool smtp_valid = false;
|
||||
|
||||
|
|
@ -302,30 +284,22 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
|||
Geary.Protocol.IMAP, this.provider
|
||||
);
|
||||
|
||||
if (this.provider == Geary.ServiceProvider.OTHER) {
|
||||
service.credentials = new Geary.Credentials(
|
||||
Geary.Credentials.Method.PASSWORD,
|
||||
this.imap_login.value.get_text().strip(),
|
||||
this.imap_password.value.get_text().strip()
|
||||
);
|
||||
service.credentials = new Geary.Credentials(
|
||||
Geary.Credentials.Method.PASSWORD,
|
||||
this.imap_login.value.get_text().strip(),
|
||||
this.imap_password.value.get_text().strip()
|
||||
);
|
||||
|
||||
Components.NetworkAddressValidator host =
|
||||
(Components.NetworkAddressValidator)
|
||||
this.imap_hostname.validator;
|
||||
GLib.NetworkAddress address = host.validated_address;
|
||||
service.host = address.hostname;
|
||||
service.port = (uint16) address.port;
|
||||
service.transport_security = this.imap_tls.value.method;
|
||||
Components.NetworkAddressValidator host =
|
||||
(Components.NetworkAddressValidator)
|
||||
this.imap_hostname.validator;
|
||||
GLib.NetworkAddress address = host.validated_address;
|
||||
service.host = address.hostname;
|
||||
service.port = (uint16) address.port;
|
||||
service.transport_security = this.imap_tls.value.method;
|
||||
|
||||
if (service.port == 0) {
|
||||
service.port = service.get_default_port();
|
||||
}
|
||||
} else {
|
||||
service.credentials = new Geary.Credentials(
|
||||
Geary.Credentials.Method.PASSWORD,
|
||||
this.email.value.get_text().strip(),
|
||||
this.imap_password.value.get_text().strip()
|
||||
);
|
||||
if (service.port == 0) {
|
||||
service.port = service.get_default_port();
|
||||
}
|
||||
|
||||
return service;
|
||||
|
|
@ -336,96 +310,121 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
|||
Geary.Protocol.SMTP, this.provider
|
||||
);
|
||||
|
||||
if (this.provider == Geary.ServiceProvider.OTHER) {
|
||||
service.credentials_requirement = this.smtp_auth.value.source;
|
||||
if (service.credentials_requirement ==
|
||||
Geary.Credentials.Requirement.CUSTOM) {
|
||||
service.credentials = new Geary.Credentials(
|
||||
Geary.Credentials.Method.PASSWORD,
|
||||
this.smtp_login.value.get_text().strip(),
|
||||
this.smtp_password.value.get_text().strip()
|
||||
);
|
||||
}
|
||||
service.credentials_requirement = this.smtp_auth.value.source;
|
||||
if (service.credentials_requirement ==
|
||||
Geary.Credentials.Requirement.CUSTOM) {
|
||||
service.credentials = new Geary.Credentials(
|
||||
Geary.Credentials.Method.PASSWORD,
|
||||
this.smtp_login.value.get_text().strip(),
|
||||
this.smtp_password.value.get_text().strip()
|
||||
);
|
||||
}
|
||||
|
||||
Components.NetworkAddressValidator host =
|
||||
(Components.NetworkAddressValidator)
|
||||
this.smtp_hostname.validator;
|
||||
GLib.NetworkAddress address = host.validated_address;
|
||||
Components.NetworkAddressValidator host =
|
||||
(Components.NetworkAddressValidator)
|
||||
this.smtp_hostname.validator;
|
||||
GLib.NetworkAddress address = host.validated_address;
|
||||
|
||||
service.host = address.hostname;
|
||||
service.port = (uint16) address.port;
|
||||
service.transport_security = this.smtp_tls.value.method;
|
||||
service.host = address.hostname;
|
||||
service.port = (uint16) address.port;
|
||||
service.transport_security = this.smtp_tls.value.method;
|
||||
|
||||
if (service.port == 0) {
|
||||
service.port = service.get_default_port();
|
||||
}
|
||||
if (service.port == 0) {
|
||||
service.port = service.get_default_port();
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
private void check_validation() {
|
||||
bool server_settings_visible = this.stack.get_visible_child_name() == "server_settings";
|
||||
bool controls_valid = true;
|
||||
foreach (Gtk.ListBox list in new Gtk.ListBox[] {
|
||||
Gtk.ListBox[] list_boxes;
|
||||
if (server_settings_visible) {
|
||||
list_boxes = new Gtk.ListBox[] {
|
||||
this.details_list, this.receiving_list, this.sending_list
|
||||
}) {
|
||||
list.foreach((child) => {
|
||||
};
|
||||
} else {
|
||||
list_boxes = new Gtk.ListBox[] { this.details_list };
|
||||
}
|
||||
foreach (Gtk.ListBox list_box in list_boxes) {
|
||||
list_box.foreach((child) => {
|
||||
AddPaneRow? validatable = child as AddPaneRow;
|
||||
if (validatable != null && !validatable.validator.is_valid) {
|
||||
controls_valid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.create_button.set_sensitive(controls_valid);
|
||||
this.action_button.set_sensitive(controls_valid);
|
||||
this.controls_valid = controls_valid;
|
||||
}
|
||||
|
||||
private void update_operation_ui(bool is_running) {
|
||||
this.create_spinner.visible = is_running;
|
||||
this.create_spinner.active = is_running;
|
||||
this.create_button.sensitive = !is_running;
|
||||
this.action_spinner.visible = is_running;
|
||||
this.action_spinner.active = is_running;
|
||||
this.action_button.sensitive = !is_running;
|
||||
this.back_button.sensitive = !is_running;
|
||||
this.sensitive = !is_running;
|
||||
}
|
||||
|
||||
private void on_validated(Components.Validator.Trigger reason) {
|
||||
check_validation();
|
||||
if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) {
|
||||
this.create_button.clicked();
|
||||
}
|
||||
private void switch_to_user_settings() {
|
||||
this.stack.set_visible_child_name("user_settings");
|
||||
this.action_button.set_label(_("_Next"));
|
||||
this.action_button.set_sensitive(true);
|
||||
this.action_button.get_style_context().remove_class("suggested-action");
|
||||
}
|
||||
|
||||
private void on_activated() {
|
||||
if (this.controls_valid) {
|
||||
this.create_button.clicked();
|
||||
}
|
||||
private void switch_to_server_settings() {
|
||||
this.stack.set_visible_child_name("server_settings");
|
||||
this.action_button.set_label(_("_Create"));
|
||||
this.action_button.set_sensitive(false);
|
||||
this.action_button.get_style_context().add_class("suggested-action");
|
||||
}
|
||||
|
||||
private void on_email_changed() {
|
||||
Gtk.Entry imap_login_entry = this.imap_login.value;
|
||||
Gtk.Entry smtp_login_entry = this.smtp_login.value;
|
||||
private void set_server_settings_from_autoconfig(AutoConfig auto_config,
|
||||
GLib.AsyncResult res)
|
||||
throws Accounts.AutoConfigError {
|
||||
AutoConfigValues auto_config_values = auto_config.get_config.end(res);
|
||||
Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
|
||||
Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
|
||||
string email = "";
|
||||
string hostname = "";
|
||||
string imap_hostname = "";
|
||||
string smtp_hostname = "";
|
||||
TlsComboBox imap_tls_combo_box = this.imap_tls.value;
|
||||
TlsComboBox smtp_tls_combo_box = this.smtp_tls.value;
|
||||
|
||||
imap_hostname_entry.text = auto_config_values.imap_server +
|
||||
":" + auto_config_values.imap_port;
|
||||
smtp_hostname_entry.text = auto_config_values.smtp_server +
|
||||
":" + auto_config_values.smtp_port;
|
||||
imap_tls_combo_box.method = auto_config_values.imap_tls_method;
|
||||
smtp_tls_combo_box.method = auto_config_values.smtp_tls_method;
|
||||
|
||||
this.imap_hostname.hide();
|
||||
this.smtp_hostname.hide();
|
||||
this.imap_tls.hide();
|
||||
this.smtp_tls.hide();
|
||||
|
||||
switch (auto_config_values.id) {
|
||||
case "googlemail.com":
|
||||
this.provider = Geary.ServiceProvider.GMAIL;
|
||||
break;
|
||||
case "hotmail.com":
|
||||
this.provider = Geary.ServiceProvider.OUTLOOK;
|
||||
break;
|
||||
default:
|
||||
this.provider = Geary.ServiceProvider.OTHER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void set_server_settings_from_hostname(string hostname) {
|
||||
Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
|
||||
Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
|
||||
string smtp_hostname = "smtp." + hostname;
|
||||
string imap_hostname = "imap." + hostname;
|
||||
string last_imap_hostname = "";
|
||||
string last_smtp_hostname = "";
|
||||
|
||||
if (this.email.validator.state == Components.Validator.Validity.VALID) {
|
||||
email = this.email.value.text;
|
||||
hostname = email.split("@")[1];
|
||||
smtp_hostname = "smtp." + hostname;
|
||||
imap_hostname = "imap." + hostname;
|
||||
}
|
||||
|
||||
if (imap_login_entry.text == this.last_valid_email) {
|
||||
imap_login_entry.text = email;
|
||||
}
|
||||
if (smtp_login_entry.text == this.last_valid_email) {
|
||||
smtp_login_entry.text = email;
|
||||
}
|
||||
this.imap_hostname.show();
|
||||
this.smtp_hostname.show();
|
||||
|
||||
if (this.last_valid_hostname != "") {
|
||||
last_imap_hostname = "imap." + this.last_valid_hostname;
|
||||
|
|
@ -437,9 +436,102 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
|||
if (smtp_hostname_entry.text == last_smtp_hostname) {
|
||||
smtp_hostname_entry.text = smtp_hostname;
|
||||
}
|
||||
this.last_valid_hostname = hostname;
|
||||
}
|
||||
|
||||
private void add_goa_account() {
|
||||
this.accounts.add_goa_account.begin(
|
||||
this.provider, this.op_cancellable,
|
||||
(obj, res) => {
|
||||
bool add_local = false;
|
||||
try {
|
||||
this.accounts.add_goa_account.end(res);
|
||||
} catch (GLib.IOError.NOT_SUPPORTED err) {
|
||||
// Not a supported type, so don't bother logging the error
|
||||
add_local = true;
|
||||
} catch (GLib.Error err) {
|
||||
debug("Failed to add %s via GOA: %s",
|
||||
this.provider.to_string(), err.message);
|
||||
add_local = true;
|
||||
}
|
||||
// Google Mail does not support "Less secure apps" anymore
|
||||
if (add_local) {
|
||||
switch (this.provider) {
|
||||
case Geary.ServiceProvider.GMAIL:
|
||||
this.editor.add_notification(
|
||||
new Components.InAppNotification(
|
||||
// Translators: In-app notification label, when
|
||||
// GNOME Online Accounts are missing
|
||||
_("Online accounts are missing")
|
||||
)
|
||||
);
|
||||
add_local = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (add_local) {
|
||||
switch_to_server_settings();
|
||||
} else {
|
||||
this.editor.pop();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void on_validated(Components.Validator.Trigger reason) {
|
||||
check_validation();
|
||||
if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) {
|
||||
this.action_button.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
private void on_activated() {
|
||||
if (this.controls_valid) {
|
||||
this.action_button.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
private void on_email_changed() {
|
||||
Gtk.Entry imap_login_entry = this.imap_login.value;
|
||||
Gtk.Entry smtp_login_entry = this.smtp_login.value;
|
||||
|
||||
this.auto_config_cancellable.cancel();
|
||||
|
||||
if (this.email.validator.state != Components.Validator.Validity.VALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
string email = this.email.value.text;
|
||||
string hostname = email.split("@")[1];
|
||||
|
||||
// Do not update entries if changed by user
|
||||
if (imap_login_entry.text == this.last_valid_email) {
|
||||
imap_login_entry.text = email;
|
||||
}
|
||||
if (smtp_login_entry.text == this.last_valid_email) {
|
||||
smtp_login_entry.text = email;
|
||||
}
|
||||
|
||||
this.last_valid_email = email;
|
||||
this.last_valid_hostname = hostname;
|
||||
|
||||
// Try to get configuration from Thunderbird autoconfig service
|
||||
this.action_spinner.visible = true;
|
||||
this.action_spinner.active = true;
|
||||
this.auto_config_cancellable = new GLib.Cancellable();
|
||||
var auto_config = new AutoConfig(this.auto_config_cancellable);
|
||||
auto_config.get_config.begin(hostname, (obj, res) => {
|
||||
try {
|
||||
set_server_settings_from_autoconfig(auto_config, res);
|
||||
} catch (Accounts.AutoConfigError err) {
|
||||
debug("Error getting auto configuration: %s", err.message);
|
||||
set_server_settings_from_hostname(hostname);
|
||||
}
|
||||
this.action_spinner.visible = false;
|
||||
this.action_spinner.active = false;
|
||||
});
|
||||
}
|
||||
|
||||
private void on_smtp_auth_changed() {
|
||||
|
|
@ -474,13 +566,29 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
|||
}
|
||||
|
||||
[GtkCallback]
|
||||
private void on_create_button_clicked() {
|
||||
this.validate_account.begin(this.op_cancellable);
|
||||
private void on_action_button_clicked() {
|
||||
if (this.stack.get_visible_child_name() == "user_settings") {
|
||||
switch (this.provider) {
|
||||
case Geary.ServiceProvider.GMAIL:
|
||||
case Geary.ServiceProvider.OUTLOOK:
|
||||
add_goa_account();
|
||||
break;
|
||||
case Geary.ServiceProvider.OTHER:
|
||||
switch_to_server_settings();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.validate_account.begin(this.op_cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
[GtkCallback]
|
||||
private void on_back_button_clicked() {
|
||||
this.editor.pop();
|
||||
if (this.stack.get_visible_child_name() == "user_settings") {
|
||||
this.editor.pop();
|
||||
} else {
|
||||
switch_to_user_settings();
|
||||
}
|
||||
}
|
||||
|
||||
[GtkCallback]
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
|||
/** {@inheritDoc} */
|
||||
internal Gtk.Widget initial_widget {
|
||||
get {
|
||||
return this.show_welcome ? this.service_list : this.accounts_list;
|
||||
return this.accounts_list;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,10 +73,6 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
|||
|
||||
[GtkChild] private unowned Gtk.Frame accounts_list_frame;
|
||||
|
||||
[GtkChild] private unowned Gtk.Label add_service_label;
|
||||
|
||||
[GtkChild] private unowned Gtk.ListBox service_list;
|
||||
|
||||
private Gee.Map<Geary.AccountInformation,EditorEditPane> edit_pane_cache =
|
||||
new Gee.HashMap<Geary.AccountInformation,EditorEditPane>();
|
||||
|
||||
|
|
@ -97,12 +93,6 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
|||
add_account(account, this.accounts.get_status(account));
|
||||
}
|
||||
|
||||
this.service_list.set_header_func(Editor.seperator_headers);
|
||||
this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.GMAIL));
|
||||
this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.OUTLOOK));
|
||||
this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.YAHOO));
|
||||
this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.OTHER));
|
||||
|
||||
this.accounts.account_added.connect(on_account_added);
|
||||
this.accounts.account_status_changed.connect(on_account_status_changed);
|
||||
this.accounts.account_removed.connect(on_account_removed);
|
||||
|
|
@ -128,8 +118,8 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
|||
base.destroy();
|
||||
}
|
||||
|
||||
internal void show_new_account(Geary.ServiceProvider provider) {
|
||||
this.editor.push(new EditorAddPane(this.editor, provider));
|
||||
internal void show_new_account() {
|
||||
this.editor.push(new EditorAddPane(this.editor));
|
||||
}
|
||||
|
||||
internal void show_existing_account(Geary.AccountInformation account) {
|
||||
|
|
@ -171,13 +161,11 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
|||
// pane and service list.
|
||||
this.welcome_panel.show();
|
||||
this.accounts_list_frame.hide();
|
||||
this.add_service_label.hide();
|
||||
} else {
|
||||
// There are some accounts available, so show them and
|
||||
// the full add service UI.
|
||||
this.welcome_panel.hide();
|
||||
this.accounts_list_frame.show();
|
||||
this.add_service_label.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -266,21 +254,9 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
|||
}
|
||||
|
||||
[GtkCallback]
|
||||
private bool on_list_keynav_failed(Gtk.Widget widget,
|
||||
Gtk.DirectionType direction) {
|
||||
bool ret = Gdk.EVENT_PROPAGATE;
|
||||
if (direction == Gtk.DirectionType.DOWN &&
|
||||
widget == this.accounts_list) {
|
||||
this.service_list.child_focus(direction);
|
||||
ret = Gdk.EVENT_STOP;
|
||||
} else if (direction == Gtk.DirectionType.UP &&
|
||||
widget == this.service_list) {
|
||||
this.accounts_list.child_focus(direction);
|
||||
ret = Gdk.EVENT_STOP;
|
||||
}
|
||||
return ret;
|
||||
private void on_add_button_clicked() {
|
||||
show_new_account();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -424,75 +400,6 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
|
|||
}
|
||||
|
||||
|
||||
private class Accounts.AddServiceProviderRow : EditorRow<EditorListPane> {
|
||||
|
||||
|
||||
internal Geary.ServiceProvider provider;
|
||||
|
||||
private Gtk.Label service_name = new Gtk.Label("");
|
||||
private Gtk.Image next_icon = new Gtk.Image.from_icon_name(
|
||||
"go-next-symbolic", Gtk.IconSize.SMALL_TOOLBAR
|
||||
);
|
||||
|
||||
|
||||
public AddServiceProviderRow(Geary.ServiceProvider provider) {
|
||||
this.provider = provider;
|
||||
|
||||
// Translators: Label for adding a generic email account
|
||||
string? name = null;
|
||||
switch (provider) {
|
||||
case Geary.ServiceProvider.GMAIL:
|
||||
name = _("Gmail");
|
||||
break;
|
||||
|
||||
case Geary.ServiceProvider.OUTLOOK:
|
||||
name = _("Outlook.com");
|
||||
break;
|
||||
|
||||
case Geary.ServiceProvider.YAHOO:
|
||||
name = _("Yahoo");
|
||||
break;
|
||||
|
||||
case Geary.ServiceProvider.OTHER:
|
||||
name = _("Other email providers");
|
||||
break;
|
||||
}
|
||||
this.service_name.set_text(name);
|
||||
this.service_name.set_hexpand(true);
|
||||
this.service_name.halign = Gtk.Align.START;
|
||||
this.service_name.show();
|
||||
|
||||
this.next_icon.show();
|
||||
|
||||
this.layout.add(this.service_name);
|
||||
this.layout.add(this.next_icon);
|
||||
}
|
||||
|
||||
public override void activated(EditorListPane pane) {
|
||||
pane.accounts.add_goa_account.begin(
|
||||
this.provider, pane.op_cancellable,
|
||||
(obj, res) => {
|
||||
bool add_local = false;
|
||||
try {
|
||||
pane.accounts.add_goa_account.end(res);
|
||||
} catch (GLib.IOError.NOT_SUPPORTED err) {
|
||||
// Not a supported type, so don't bother logging the error
|
||||
add_local = true;
|
||||
} catch (GLib.Error err) {
|
||||
debug("Failed to add %s via GOA: %s",
|
||||
this.provider.to_string(), err.message);
|
||||
add_local = true;
|
||||
}
|
||||
|
||||
if (add_local) {
|
||||
pane.show_new_account(this.provider);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
internal class Accounts.ReorderAccountCommand : Application.Command {
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ client_vala_sources = files(
|
|||
'application/goa-mediator.vala',
|
||||
'application/secret-mediator.vala',
|
||||
|
||||
'accounts/accounts-autoconfig.vala',
|
||||
'accounts/accounts-editor.vala',
|
||||
'accounts/accounts-editor-add-pane.vala',
|
||||
'accounts/accounts-editor-edit-pane.vala',
|
||||
|
|
|
|||
|
|
@ -7,34 +7,29 @@
|
|||
<property name="title" translatable="yes">Add an account</property>
|
||||
<property name="has_subtitle">False</property>
|
||||
<child>
|
||||
<object class="GtkGrid">
|
||||
<object class="GtkButton" id="back_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="clicked" handler="on_back_button_clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkButton" id="back_button">
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="clicked" handler="on_back_button_clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="icon_name">go-previous-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="icon_name">go-previous-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">start</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="column_spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="create_spinner">
|
||||
<object class="GtkSpinner" id="action_spinner">
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
|
@ -43,16 +38,13 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="create_button">
|
||||
<property name="label" translatable="yes">_Create</property>
|
||||
<object class="GtkButton" id="action_button">
|
||||
<property name="label" translatable="yes">_Next</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="clicked" handler="on_create_button_clicked" swapped="no"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
<signal name="clicked" handler="on_action_button_clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
|
@ -89,13 +81,14 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="margin">24</property>
|
||||
<child>
|
||||
<object class="GtkGrid" id="pane_content">
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="valign">start</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="details_list">
|
||||
<property name="visible">True</property>
|
||||
|
|
@ -105,102 +98,98 @@
|
|||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="name">user_settings</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="receiving_panel">
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<object class="GtkGrid" id="receiving_panel">
|
||||
<property name="visible">True</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Receiving</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
<style>
|
||||
<class name="geary-settings-heading"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="receiving_list">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Receiving</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
<style>
|
||||
<class name="geary-settings-heading"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="receiving_list">
|
||||
<property name="visible">True</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="sending_panel">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Sending</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
<style>
|
||||
<class name="geary-settings-heading"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="sending_list">
|
||||
<property name="visible">True</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="name">server_settings</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="sending_panel">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Sending</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
<style>
|
||||
<class name="geary-settings-heading"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="sending_list">
|
||||
<property name="visible">True</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="geary-accounts-editor-pane-content"/>
|
||||
</style>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,19 @@
|
|||
<property name="title" translatable="yes">Accounts</property>
|
||||
<property name="has_subtitle">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_button">
|
||||
<property name="label" translatable="yes">_Add</property>
|
||||
<property name="tooltip_text" translatable="yes">Add an account</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="clicked" handler="on_add_button_clicked"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="pane_adjustment">
|
||||
<property name="upper">100</property>
|
||||
|
|
@ -103,7 +116,6 @@
|
|||
<object class="GtkListBox" id="accounts_list">
|
||||
<property name="visible">True</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
|
||||
<signal name="row-activated" handler="on_row_activated" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
|
|
@ -115,53 +127,9 @@
|
|||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="add_service_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Add an account</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
<style>
|
||||
<class name="geary-settings-heading"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="service_list">
|
||||
<property name="width_request">0</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
|
||||
<signal name="row-activated" handler="on_row_activated" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="geary-accounts-editor-pane-content"/>
|
||||
</style>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue