diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt
index 62c73eca..9b3e991a 100644
--- a/libdino/CMakeLists.txt
+++ b/libdino/CMakeLists.txt
@@ -40,6 +40,7 @@ SOURCES
src/service/muc_manager.vala
src/service/notification_events.vala
src/service/presence_manager.vala
+ src/service/registration.vala
src/service/roster_manager.vala
src/service/stream_interactor.vala
src/service/util.vala
diff --git a/libdino/src/service/module_manager.vala b/libdino/src/service/module_manager.vala
index 78819bb3..dac5aef2 100644
--- a/libdino/src/service/module_manager.vala
+++ b/libdino/src/service/module_manager.vala
@@ -76,6 +76,7 @@ public class ModuleManager {
module_map[account].add(new Xep.Ping.Module());
module_map[account].add(new Xep.DelayedDelivery.Module());
module_map[account].add(new StreamError.Module());
+ module_map[account].add(new Xep.InBandRegistration.Module());
initialize_account_modules(account, module_map[account]);
}
}
diff --git a/libdino/src/service/registration.vala b/libdino/src/service/registration.vala
new file mode 100644
index 00000000..32d8b04b
--- /dev/null
+++ b/libdino/src/service/registration.vala
@@ -0,0 +1,42 @@
+using Gee;
+
+using Xmpp;
+using Dino.Entities;
+
+namespace Dino {
+
+public class Register {
+
+ public static async Xep.InBandRegistration.Form get_registration_form(Jid jid) {
+ XmppStream stream = new XmppStream();
+ stream.add_module(new Tls.Module());
+ stream.add_module(new Iq.Module());
+ stream.add_module(new Xep.InBandRegistration.Module());
+ stream.connect.begin(jid.bare_jid.to_string());
+
+ Xep.InBandRegistration.Form? form = null;
+ SourceFunc callback = get_registration_form.callback;
+ stream.stream_negotiated.connect(() => {
+ if (callback != null) {
+ Idle.add((owned)callback);
+ }
+ });
+ Timeout.add_seconds(5, () => {
+ if (callback != null) {
+ Idle.add((owned)callback);
+ }
+ return false;
+ });
+ yield;
+ if (stream.negotiation_complete) {
+ form = yield stream.get_module(Xep.InBandRegistration.Module.IDENTITY).get_from_server(stream, jid);
+ }
+ return form;
+ }
+
+ public static async string submit_form(Jid jid, Xep.InBandRegistration.Form form) {
+ return yield form.stream.get_module(Xep.InBandRegistration.Module.IDENTITY).submit_to_server(form.stream, jid, form);
+ }
+}
+
+}
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 9c5b06ff..00ca692a 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -124,6 +124,7 @@ SOURCES
src/ui/settings_dialog.vala
src/ui/unified_window.vala
src/ui/util/accounts_combo_box.vala
+ src/ui/util/data_forms.vala
src/ui/util/helper.vala
src/ui/util/label_hybrid.vala
src/ui/util/preview_file_chooser_native.vala
diff --git a/main/data/manage_accounts/add_account_dialog.ui b/main/data/manage_accounts/add_account_dialog.ui
index 44c131c3..6698b337 100644
--- a/main/data/manage_accounts/add_account_dialog.ui
+++ b/main/data/manage_accounts/add_account_dialog.ui
@@ -1,136 +1,390 @@
- 300
+ 400
True
-
-
-
diff --git a/main/src/ui/contact_details/muc_config_form_provider.vala b/main/src/ui/contact_details/muc_config_form_provider.vala
index 072627bf..a088bd97 100644
--- a/main/src/ui/contact_details/muc_config_form_provider.vala
+++ b/main/src/ui/contact_details/muc_config_form_provider.vala
@@ -74,57 +74,9 @@ public class MucConfigFormProvider : Plugins.ContactDetailsProvider, Object {
}
}
- Widget? widget = get_widget(field);
+ Widget? widget = Util.get_data_form_fild_widget(field);
if (widget != null) contact_details.add(_("Room Configuration"), label, desc, widget);
}
-
- private static Widget? get_widget(DataForms.DataForm.Field field) {
- if (field.type_ == null) return null;
- switch (field.type_) {
- case DataForms.DataForm.Type.BOOLEAN:
- DataForms.DataForm.BooleanField boolean_field = field as DataForms.DataForm.BooleanField;
- Switch sw = new Switch() { active=boolean_field.value, valign=Align.CENTER, visible=true };
- sw.state_set.connect((state) => {
- boolean_field.value = state;
- return false;
- });
- return sw;
- case DataForms.DataForm.Type.JID_MULTI:
- return null;
- case DataForms.DataForm.Type.LIST_SINGLE:
- DataForms.DataForm.ListSingleField list_single_field = field as DataForms.DataForm.ListSingleField;
- ComboBoxText combobox = new ComboBoxText() { valign=Align.CENTER, visible=true };
- for (int i = 0; i < list_single_field.options.size; i++) {
- DataForms.DataForm.Option option = list_single_field.options[i];
- combobox.append(option.value, option.label);
- if (option.value == list_single_field.value) combobox.active = i;
- }
- combobox.changed.connect(() => {
- list_single_field.value = combobox.get_active_id();
- });
- return combobox;
- case DataForms.DataForm.Type.LIST_MULTI:
- return null;
- case DataForms.DataForm.Type.TEXT_PRIVATE:
- DataForms.DataForm.TextPrivateField text_private_field = field as DataForms.DataForm.TextPrivateField;
- Entry entry = new Entry() { text=text_private_field.value ?? "", valign=Align.CENTER, visible=true, visibility=false };
- entry.key_release_event.connect(() => {
- text_private_field.value = entry.text;
- return false;
- });
- return entry;
- case DataForms.DataForm.Type.TEXT_SINGLE:
- DataForms.DataForm.TextSingleField text_single_field = field as DataForms.DataForm.TextSingleField;
- Entry entry = new Entry() { text=text_single_field.value ?? "", valign=Align.CENTER, visible=true };
- entry.key_release_event.connect(() => {
- text_single_field.value = entry.text;
- return false;
- });
- return entry;
- default:
- return null;
- }
- }
}
}
diff --git a/main/src/ui/manage_accounts/add_account_dialog.vala b/main/src/ui/manage_accounts/add_account_dialog.vala
index 5715db51..f9ab794e 100644
--- a/main/src/ui/manage_accounts/add_account_dialog.vala
+++ b/main/src/ui/manage_accounts/add_account_dialog.vala
@@ -11,29 +11,120 @@ public class AddAccountDialog : Gtk.Dialog {
public signal void added(Account account);
- [GtkChild] private Button cancel_button;
- [GtkChild] private Button ok_button;
- [GtkChild] private Entry alias_entry;
+ [GtkChild] private Stack stack;
+
+ [GtkChild] private Revealer notification_revealer;
+ [GtkChild] private Label notification_label;
+
+ // Sign in
+ [GtkChild] private Box sign_in_box;
[GtkChild] private Entry jid_entry;
+ [GtkChild] private Entry alias_entry;
[GtkChild] private Entry password_entry;
+ [GtkChild] private Button sign_in_continue;
+ [GtkChild] private Button serverlist_button;
+
+ // Select Server
+ [GtkChild] private Box create_account_box;
+ [GtkChild] private Button login_button;
+ [GtkChild] private Stack select_server_continue_stack;
+ [GtkChild] private Button select_server_continue;
+ [GtkChild] private Label register_form_continue_label;
+ [GtkChild] private ListBox server_list_box;
+ [GtkChild] private Entry server_entry;
+
+ // Register Form
+ [GtkChild] private Box register_box;
+ [GtkChild] private Label register_title;
+ [GtkChild] private Box form_box;
+ [GtkChild] private Button register_form_back;
+ [GtkChild] private Stack register_form_continue_stack;
+ [GtkChild] private Button register_form_continue;
+
+ private static string[] server_list = new string[]{
+ "5222.de",
+ "jabber.fr",
+ "movim.eu",
+ "yax.im"
+ };
+ private HashMap list_box_jids = new HashMap();
+ private Jid? server_jid = null;
+ private Xep.InBandRegistration.Form? form = null;
public AddAccountDialog(StreamInteractor stream_interactor) {
- Object(use_header_bar : 1);
this.title = _("Add Account");
- cancel_button.clicked.connect(() => { close(); });
- ok_button.clicked.connect(on_ok_button_clicked);
+ // Sign in
jid_entry.changed.connect(on_jid_entry_changed);
jid_entry.focus_out_event.connect(on_jid_entry_focus_out_event);
+ sign_in_continue.clicked.connect(on_sign_in_continue_clicked);
+ serverlist_button.clicked.connect(show_select_server);
+
+ // Select Server
+ server_entry.changed.connect(() => {
+ Jid? jid = Jid.parse(server_entry.text);
+ select_server_continue.sensitive = jid != null && jid.localpart == null && jid.resourcepart == null;
+ });
+ select_server_continue.clicked.connect(on_select_server_continue);
+ login_button.clicked.connect(show_sign_in);
+
+ foreach (string server in server_list) {
+ ListBoxRow list_box_row = new ListBoxRow() { visible=true };
+ list_box_row.add(new Label(server) { xalign=0, margin=3, margin_start=7, margin_end=7, visible=true });
+ list_box_jids[list_box_row] = server;
+ server_list_box.add(list_box_row);
+ }
+
+ // Register Form
+ register_form_continue.clicked.connect(on_register_form_continue_clicked);
+ register_form_back.clicked.connect(show_select_server);
+
+ show_sign_in();
+ }
+
+ private void show_sign_in() {
+ sign_in_box.visible = true;
+ stack.visible_child_name = "login";
+ create_account_box.visible = false;
+ register_box.visible = false;
+ set_default(sign_in_continue);
+ animate_window_resize(sign_in_box);
+ }
+
+ private void show_select_server() {
+ server_entry.text = "";
+ server_entry.grab_focus();
+ set_default(select_server_continue);
+
+ server_list_box.row_selected.disconnect(on_server_list_row_selected);
+ server_list_box.unselect_all();
+ server_list_box.row_selected.connect(on_server_list_row_selected);
+
+ create_account_box.visible = true;
+ stack.visible_child_name = "server";
+ sign_in_box.visible = false;
+ register_box.visible = false;
+
+ animate_window_resize(create_account_box);
+ }
+
+ private void show_register_form() {
+ register_box.visible = true;
+ stack.visible_child_name = "form";
+ sign_in_box.visible = false;
+ create_account_box.visible = false;
+
+ set_default(register_form_continue);
+ animate_window_resize(register_box);
}
private void on_jid_entry_changed() {
Jid? jid = Jid.parse(jid_entry.text);
if (jid != null && jid.localpart != null && jid.resourcepart == null) {
- ok_button.set_sensitive(true);
+ sign_in_continue.set_sensitive(true);
jid_entry.secondary_icon_name = null;
} else {
- ok_button.set_sensitive(false);
+ sign_in_continue.set_sensitive(false);
}
}
@@ -41,7 +132,6 @@ public class AddAccountDialog : Gtk.Dialog {
Jid? jid = Jid.parse(jid_entry.text);
if (jid == null || jid.localpart == null || jid.resourcepart != null) {
jid_entry.secondary_icon_name = "dialog-warning-symbolic";
- // TODO why doesn't the tooltip work
jid_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY, _("JID should be of the form “user@example.com”"));
} else {
jid_entry.secondary_icon_name = null;
@@ -49,13 +139,131 @@ public class AddAccountDialog : Gtk.Dialog {
return false;
}
- private void on_ok_button_clicked() {
+ private void on_sign_in_continue_clicked() {
Jid jid = new Jid(jid_entry.get_text());
string password = password_entry.get_text();
string alias = alias_entry.get_text();
+ store_account(jid, password, alias);
+ close();
+ }
+
+ private void on_select_server_continue() {
+ server_jid = new Jid(server_entry.text);
+ request_show_register_form.begin();
+ }
+
+ private void on_server_list_row_selected(ListBox box, ListBoxRow? row) {
+ server_jid = new Jid(list_box_jids[row]);
+ request_show_register_form.begin();
+ }
+
+ private async void request_show_register_form() {
+ select_server_continue_stack.visible_child_name = "spinner";
+ form = yield Register.get_registration_form(server_jid);
+ if (select_server_continue_stack == null) {
+ return;
+ }
+ select_server_continue_stack.visible_child_name = "label";
+ if (form != null) {
+ set_register_form(server_jid, form);
+ show_register_form();
+ } else {
+ display_notification(_("No response from server"));
+ }
+ }
+
+ private void set_register_form(Jid server, Xep.InBandRegistration.Form form) {
+ form_box.foreach((widget) => { widget.destroy(); });
+ register_title.label = _("Register on %s").printf(server.to_string());
+
+ if (form.oob != null) {
+ form_box.add(new Label(_("The server requires to sign up through a website")){ use_markup=true, visible=true } );
+ form_box.add(new Label(@"$(form.oob)") { use_markup=true, visible=true });
+ register_form_continue_label.label = _("Open Registration");
+ register_form_continue.visible = true;
+ register_form_continue.grab_focus();
+ } else if (form.fields.size > 0) {
+ int i = 0;
+ foreach (Xep.DataForms.DataForm.Field field in form.fields) {
+ if (field.label != null && field.label != "") {
+ form_box.add(new Label(field.label) { xalign=0, margin_top=7, visible=true });
+ }
+ Widget field_widget = Util.get_data_form_fild_widget(field);
+ if (field_widget != null) {
+ form_box.add(field_widget);
+ }
+ i++;
+ }
+ register_form_continue.visible = true;
+ register_form_continue_label.label = _("Register");
+ } else {
+ form_box.add(new Label(_("Check %s for information on how to sign up").printf(@"$(server)")) { use_markup=true, visible=true });
+ register_form_continue.visible = false;
+ }
+ }
+
+ private async void on_register_form_continue_clicked() {
+ notification_revealer.set_reveal_child(false);
+ // Button is opening a registration website
+ if (form.oob != null) {
+ try {
+ AppInfo.launch_default_for_uri(form.oob, null);
+ } catch (Error e) { }
+ show_sign_in();
+ return;
+ }
+
+ register_form_continue_stack.visible_child_name = "spinner";
+ string? error = yield Register.submit_form(server_jid, form);
+ if (register_form_continue_stack == null) {
+ return;
+ }
+ register_form_continue_stack.visible_child_name = "label";
+ if (error == null) {
+ string? username = null, password = null;
+ foreach (Xep.DataForms.DataForm.Field field in form.fields) {
+ switch (field.var) {
+ case "username": username = field.get_value_string(); break;
+ case "password": password = field.get_value_string(); break;
+ }
+ }
+ store_account(new Jid(username + "@" + server_jid.domainpart), password, "");
+ close();
+ } else {
+ display_notification(error);
+ }
+ }
+
+ private void store_account(Jid jid, string password, string? alias) {
Account account = new Account(jid, null, password, alias);
added(account);
- close();
+ }
+
+ private void display_notification(string text) {
+ notification_label.label = text;
+ notification_revealer.set_reveal_child(true);
+ Timeout.add_seconds(5, () => {
+ notification_revealer.set_reveal_child(false);
+ return false;
+ });
+ }
+
+ private void animate_window_resize(Widget widget) { // TODO code duplication
+ int def_height, curr_width, curr_height;
+ get_size(out curr_width, out curr_height);
+ widget.get_preferred_height(null, out def_height);
+ def_height += 5;
+ int difference = def_height - curr_height;
+ Timer timer = new Timer();
+ Timeout.add((int) (stack.transition_duration / 30),
+ () => {
+ ulong microsec;
+ timer.elapsed(out microsec);
+ ulong millisec = microsec / 1000;
+ double partial = double.min(1, (double) millisec / stack.transition_duration);
+ resize(curr_width, (int) (curr_height + difference * partial));
+ return millisec < stack.transition_duration;
+ });
}
}
diff --git a/main/src/ui/util/data_forms.vala b/main/src/ui/util/data_forms.vala
new file mode 100644
index 00000000..11308462
--- /dev/null
+++ b/main/src/ui/util/data_forms.vala
@@ -0,0 +1,57 @@
+using Gee;
+using Gtk;
+
+using Dino.Entities;
+using Xmpp.Xep;
+
+namespace Dino.Ui.Util {
+
+public static Widget? get_data_form_fild_widget(DataForms.DataForm.Field field) {
+ if (field.type_ == null) return null;
+ switch (field.type_) {
+ case DataForms.DataForm.Type.BOOLEAN:
+ DataForms.DataForm.BooleanField boolean_field = field as DataForms.DataForm.BooleanField;
+ Switch sw = new Switch() { active=boolean_field.value, valign=Align.CENTER, visible=true };
+ sw.state_set.connect((state) => {
+ boolean_field.value = state;
+ return false;
+ });
+ return sw;
+ case DataForms.DataForm.Type.JID_MULTI:
+ return null;
+ case DataForms.DataForm.Type.LIST_SINGLE:
+ DataForms.DataForm.ListSingleField list_single_field = field as DataForms.DataForm.ListSingleField;
+ ComboBoxText combobox = new ComboBoxText() { valign=Align.CENTER, visible=true };
+ for (int i = 0; i < list_single_field.options.size; i++) {
+ DataForms.DataForm.Option option = list_single_field.options[i];
+ combobox.append(option.value, option.label);
+ if (option.value == list_single_field.value) combobox.active = i;
+ }
+ combobox.changed.connect(() => {
+ list_single_field.value = combobox.get_active_id();
+ });
+ return combobox;
+ case DataForms.DataForm.Type.LIST_MULTI:
+ return null;
+ case DataForms.DataForm.Type.TEXT_PRIVATE:
+ DataForms.DataForm.TextPrivateField text_private_field = field as DataForms.DataForm.TextPrivateField;
+ Entry entry = new Entry() { text=text_private_field.value ?? "", valign=Align.CENTER, visible=true, visibility=false };
+ entry.key_release_event.connect(() => {
+ text_private_field.value = entry.text;
+ return false;
+ });
+ return entry;
+ case DataForms.DataForm.Type.TEXT_SINGLE:
+ DataForms.DataForm.TextSingleField text_single_field = field as DataForms.DataForm.TextSingleField;
+ Entry entry = new Entry() { text=text_single_field.value ?? "", valign=Align.CENTER, visible=true };
+ entry.key_release_event.connect(() => {
+ text_single_field.value = entry.text;
+ return false;
+ });
+ return entry;
+ default:
+ return null;
+ }
+}
+
+}
diff --git a/xmpp-vala/CMakeLists.txt b/xmpp-vala/CMakeLists.txt
index 6734f0ee..1649411e 100644
--- a/xmpp-vala/CMakeLists.txt
+++ b/xmpp-vala/CMakeLists.txt
@@ -55,6 +55,7 @@ SOURCES
"src/module/xep/0054_vcard/module.vala"
"src/module/xep/0060_pubsub.vala"
"src/module/xep/0066_out_of_band_data.vala"
+ "src/module/xep/0077_in_band_registration.vala"
"src/module/xep/0082_date_time_profiles.vala"
"src/module/xep/0084_user_avatars.vala"
"src/module/xep/0085_chat_state_notifications.vala"
diff --git a/xmpp-vala/src/module/stanza_error.vala b/xmpp-vala/src/module/stanza_error.vala
index 51aa2629..c45ff4e3 100644
--- a/xmpp-vala/src/module/stanza_error.vala
+++ b/xmpp-vala/src/module/stanza_error.vala
@@ -36,6 +36,10 @@ namespace Xmpp {
get { return error_node.get_attribute("by"); }
}
+ public string? text {
+ get { return error_node.get_deep_string_content("urn:ietf:params:xml:ns:xmpp-stanzas:text"); }
+ }
+
public string condition {
get {
Gee.List subnodes = error_node.sub_nodes;
@@ -57,7 +61,7 @@ namespace Xmpp {
}
public StanzaNode stanza;
- private StanzaNode error_node;
+ public StanzaNode error_node;
public ErrorStanza.from_stanza(StanzaNode stanza) {
this.stanza = stanza;
diff --git a/xmpp-vala/src/module/xep/0004_data_forms.vala b/xmpp-vala/src/module/xep/0004_data_forms.vala
index 69c14b08..cc0a1a28 100644
--- a/xmpp-vala/src/module/xep/0004_data_forms.vala
+++ b/xmpp-vala/src/module/xep/0004_data_forms.vala
@@ -78,7 +78,7 @@ public class DataForm {
return ret;
}
- internal string get_value_string() {
+ public string get_value_string() {
Gee.List values = get_values();
return values.size > 0 ? values[0] : "";
}
@@ -197,7 +197,7 @@ public class DataForm {
// TODO text-multi
- internal DataForm.from_node(StanzaNode node, XmppStream stream, owned OnResult listener) {
+ internal DataForm.from_node(StanzaNode node, XmppStream stream, owned OnResult? listener = null) {
this.stanza_node = node;
this.stream = stream;
this.on_result = (owned)listener;
diff --git a/xmpp-vala/src/module/xep/0077_in_band_registration.vala b/xmpp-vala/src/module/xep/0077_in_band_registration.vala
new file mode 100644
index 00000000..1c544c18
--- /dev/null
+++ b/xmpp-vala/src/module/xep/0077_in_band_registration.vala
@@ -0,0 +1,64 @@
+using Gee;
+
+namespace Xmpp.Xep.InBandRegistration {
+
+public const string NS_URI = "jabber:iq:register";
+
+public class Module : XmppStreamNegotiationModule {
+ public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0077_in_band_registration");
+
+ public async Form? get_from_server(XmppStream stream, Jid jid) {
+ Iq.Stanza request_form_iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI).add_self_xmlns());
+ request_form_iq.to = jid;
+ SourceFunc callback = get_from_server.callback;
+ Form? form = null;
+ stream.get_module(Iq.Module.IDENTITY).send_iq(stream, request_form_iq, (stream, response_iq) => {
+ form = new Form.from_node(stream, response_iq);
+ Idle.add((owned)callback);
+ });
+ yield;
+ return form;
+ }
+
+ public async string submit_to_server(XmppStream stream, Jid jid, Form form) {
+ StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns();
+ query_node.put_node(form.get_submit_node());
+ Iq.Stanza iq = new Iq.Stanza.set(query_node);
+ iq.to = jid;
+ string? error_message = null;
+ SourceFunc callback = submit_to_server.callback;
+ stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, response_iq) => {
+ if (response_iq.is_error()) {
+ ErrorStanza? error_stanza = response_iq.get_error();
+ error_message = error_stanza.text ?? "Error";
+ }
+ Idle.add((owned)callback);
+ });
+ yield;
+ return error_message;
+ }
+
+ public override bool mandatory_outstanding(XmppStream stream) { return false; }
+
+ public override bool negotiation_active(XmppStream stream) { return false; }
+
+ public override void attach(XmppStream stream) { }
+
+ public override void detach(XmppStream stream) { }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return IDENTITY.id; }
+}
+
+public class Form : DataForms.DataForm {
+ public string? oob = null;
+
+ internal Form.from_node(XmppStream stream, Iq.Stanza iq) {
+ StanzaNode? x_node = iq.stanza.get_deep_subnode(NS_URI + ":query", DataForms.NS_URI + ":x");
+ base.from_node(x_node ?? new StanzaNode.build("x", NS_URI).add_self_xmlns(), stream);
+
+ oob = iq.stanza.get_deep_string_content(NS_URI + ":query", "jabber:x:oob:x", "url");
+ }
+}
+
+}