diff options
-rwxr-xr-x | configure | 25 | ||||
-rw-r--r-- | libdino/CMakeLists.txt | 1 | ||||
-rw-r--r-- | libdino/src/service/module_manager.vala | 1 | ||||
-rw-r--r-- | libdino/src/service/muc_manager.vala | 2 | ||||
-rw-r--r-- | libdino/src/service/registration.vala | 42 | ||||
-rw-r--r-- | main/CMakeLists.txt | 1 | ||||
-rw-r--r-- | main/data/manage_accounts/add_account_dialog.ui | 478 | ||||
-rw-r--r-- | main/src/ui/contact_details/muc_config_form_provider.vala | 50 | ||||
-rw-r--r-- | main/src/ui/manage_accounts/add_account_dialog.vala | 230 | ||||
-rw-r--r-- | main/src/ui/util/data_forms.vala | 57 | ||||
-rw-r--r-- | xmpp-vala/CMakeLists.txt | 1 | ||||
-rw-r--r-- | xmpp-vala/src/module/stanza_error.vala | 6 | ||||
-rw-r--r-- | xmpp-vala/src/module/xep/0004_data_forms.vala | 4 | ||||
-rw-r--r-- | xmpp-vala/src/module/xep/0077_in_band_registration.vala | 64 |
14 files changed, 774 insertions, 188 deletions
@@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh OPTS=`getopt -o "h" --long \ help,fetch-only,no-debug,disable-fast-vapi,with-tests,\ @@ -103,8 +103,8 @@ EOF while true; do case "$1" in --prefix ) PREFIX="$2"; shift; shift ;; - --enable-plugin ) if [ "$ENABLED_PLUGINS" == "" ]; then ENABLED_PLUGINS="$2"; else ENABLED_PLUGINS="ENABLED_PLUGINS;$2"; fi; shift; shift ;; - --disable-plugin ) if [ "$DISABLED_PLUGINS" == "" ]; then DISABLED_PLUGINS="$2"; else DISABLED_PLUGINS="DISABLED_PLUGINS;$2"; fi; shift; shift ;; + --enable-plugin ) if [ -z "$ENABLED_PLUGINS" ]; then ENABLED_PLUGINS="$2"; else ENABLED_PLUGINS="ENABLED_PLUGINS;$2"; fi; shift; shift ;; + --disable-plugin ) if [ -z "$DISABLED_PLUGINS" ]; then DISABLED_PLUGINS="$2"; else DISABLED_PLUGINS="DISABLED_PLUGINS;$2"; fi; shift; shift ;; --valac ) VALA_EXECUTABLE="$2"; shift; shift ;; --valac-flags ) VALAC_FLAGS="$2"; shift; shift ;; --lib-suffix ) LIB_SUFFIX="$2"; shift; shift ;; @@ -130,8 +130,8 @@ while true; do # Ignore for autotools compat --host | --build ) shift; shift ;; --disable-dependency-tracking | --enable-dependency-tracking ) shift ;; - # Ignore for debian compat - --disable-silent-rules | --disable-maintainer-mode ) shift ;; + # Ignore for debian compat + --disable-silent-rules | --disable-maintainer-mode ) shift ;; -h | --help ) help; exit 0 ;; -- ) shift; break ;; * ) break ;; @@ -145,7 +145,7 @@ else for i in $(cat .gitmodules | grep -n submodule | awk -F ':' '{print $1}') $(wc -l .gitmodules | awk '{print $1}'); do if ! [ $tmp -eq 0 ]; then name=$(cat .gitmodules | head -n $tmp | tail -n 1 | awk -F '"' '{print $2}') - def=$(cat .gitmodules | head -n $i | tail -n $(($i-$tmp)) | awk -F ' ' '{print $1 $2 $3}') + def=$(cat .gitmodules | head -n $i | tail -n $(expr "$i" - "$tmp") | awk -F ' ' '{print $1 $2 $3}') path=$(echo "$def" | grep '^path=' | awk -F '=' '{print $2}') url=$(echo "$def" | grep '^url=' | awk -F '=' '{print $2}') branch=$(echo "$def" | grep '^branch=' | awk -F '=' '{print $2}') @@ -161,14 +161,15 @@ else echo "Failed retrieving missing files: $res" exit 5 fi - if [[ "$branch" != "" ]]; then - pushd "$path" > /dev/null + if [ -n "$branch" ]; then + olddir="$(pwd)" + cd "$path" res=$(git checkout "$branch" 2>&1) if ! [ $? -eq 0 ]; then echo "Failed retrieving missing files: $res" exit 5 fi - popd > /dev/null + cd "$olddir" fi echo "Submodule path '$path': checked out '$branch' (via git clone)" fi @@ -177,7 +178,7 @@ else done fi -if [[ "$FETCH_ONLY" == "yes" ]]; then exit 0; fi +if [ "$FETCH_ONLY" = "yes" ]; then exit 0; fi if [ ! -x "$(which cmake 2>/dev/null)" ] then @@ -196,7 +197,7 @@ if [ -x "$ninja_bin" ]; then cmake_type="Ninja" exec_bin="$ninja_bin" exec_command="$exec_bin" - elif [[ "/usr/sbin/ninja" == "$ninja_bin" ]]; then + elif [ "/usr/sbin/ninja" = "$ninja_bin" ]; then echo "-- Ninja at $ninja_bin is not usable. Did you install 'ninja' instead of 'ninja-build'?" fi fi @@ -257,7 +258,7 @@ cmake -G "$cmake_type" \ -DLIB_INSTALL_DIR="$LIBDIR" \ .. || exit 9 -if [ "$cmake_type" == "Ninja" ] +if [ "$cmake_type" = "Ninja" ] then cat << EOF > Makefile default: diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt index 054e2bab..f45d08f5 100644 --- a/libdino/CMakeLists.txt +++ b/libdino/CMakeLists.txt @@ -41,6 +41,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/search_processor.vala src/service/stream_interactor.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/muc_manager.vala b/libdino/src/service/muc_manager.vala index b69d71f2..a0f7fe70 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -125,7 +125,7 @@ public class MucManager : StreamInteractionModule, Object { } public bool is_groupchat_occupant(Jid jid, Account account) { - return is_groupchat(jid.bare_jid, account) && jid.is_full(); + return is_groupchat(jid.bare_jid, account) && jid.resourcepart != null; } public void get_bookmarks(Account account, owned Xep.Bookmarks.Module.OnResult listener) { 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 49b1a9fc..65d84bdd 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -125,6 +125,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 @@ <?xml version="1.0" encoding="UTF-8"?> <interface> <template class="DinoUiManageAccountsAddAccountDialog"> - <property name="default_width">300</property> + <property name="default_width">400</property> <property name="modal">True</property> - <child type="titlebar"> - <object class="GtkHeaderBar"> - <property name="visible">True</property> - <child> - <object class="GtkButton" id="cancel_button"> - <property name="label" translatable="yes">Cancel</property> - <property name="visible">True</property> - </object> - <packing> - <property name="pack_type">start</property> - </packing> - </child> - <child> - <object class="GtkButton" id="ok_button"> - <property name="can_default">True</property> - <property name="label" translatable="yes">Save</property> - <property name="sensitive">False</property> - <property name="visible">True</property> - <style> - <class name="suggested-action"/> - </style> - </object> - <packing> - <property name="pack_type">end</property> - </packing> - </child> - </object> - </child> <child internal-child="vbox"> <object class="GtkBox"> <property name="visible">True</property> <child> - <object class="GtkGrid" id="info_grid"> - <property name="orientation">vertical</property> - <property name="margin">20</property> - <property name="column-spacing">10</property> - <property name="row-spacing">7</property> + <object class="GtkOverlay"> <property name="visible">True</property> <child> - <object class="GtkLabel"> - <property name="label">JID</property> - <property name="xalign">1</property> - <property name="visible">True</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="jid_entry"> - <property name="activates_default">True</property> - <property name="hexpand">True</property> - <property name="width_request">200</property> - <property name="visible">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="yes">Password</property> - <property name="xalign">1</property> + <object class="GtkBox"> + <property name="expand">True</property> <property name="visible">True</property> + <child> + <object class="GtkStack" id="stack"> + <property name="transition_type">slide-left</property> + <property name="expand">True</property> + <property name="visible">True</property> + <child> + <object class="GtkBox" id="sign_in_box"> + <property name="orientation">vertical</property> + <property name="margin">20</property> + <property name="margin-start">50</property> + <property name="margin-end">50</property> + <property name="visible">True</property> + <child> + <object class="GtkLabel"> + <property name="label">Sign in</property> + <property name="margin-bottom">10</property> + <property name="visible">True</property> + <attributes> + <attribute name="scale" value="1.3"/> + </attributes> + </object> + </child> + <child> + <object class="GtkBox" id="info_grid"> + <property name="orientation">vertical</property> + <property name="visible">True</property> + <child> + <object class="GtkLabel"> + <property name="label">JID</property> + <property name="xalign">0</property> + <property name="visible">True</property> + <attributes> + <attribute name="scale" value="0.9"/> + </attributes> + </object> + </child> + <child> + <object class="GtkEntry" id="jid_entry"> + <property name="activates_default">True</property> + <property name="hexpand">True</property> + <property name="width_request">200</property> + <property name="visible">True</property> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Password</property> + <property name="xalign">0</property> + <property name="margin-top">7</property> + <property name="visible">True</property> + <attributes> + <attribute name="scale" value="0.9"/> + </attributes> + </object> + </child> + <child> + <object class="GtkEntry" id="password_entry"> + <property name="activates_default">True</property> + <property name="hexpand">True</property> + <property name="input_purpose">password</property> + <property name="width_request">200</property> + <property name="visible">True</property> + <property name="visibility">False</property> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Local alias</property> + <property name="xalign">0</property> + <property name="margin-top">7</property> + <property name="visible">True</property> + <attributes> + <attribute name="scale" value="0.9"/> + </attributes> + </object> + </child> + <child> + <object class="GtkEntry" id="alias_entry"> + <property name="activates_default">True</property> + <property name="hexpand">True</property> + <property name="width_request">200</property> + <property name="visible">True</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="orientation">horizontal</property> + <property name="margin-top">20</property> + <property name="visible">True</property> + <child> + <object class="GtkButton" id="serverlist_button"> + <property name="label" translatable="yes">Create account</property> + <property name="visible">True</property> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + <child> + <object class="GtkButton" id="sign_in_continue"> + <property name="can_default">True</property> + <property name="label" translatable="yes">Save</property> + <property name="sensitive">False</property> + <property name="visible">True</property> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="name">login</property> + </packing> + </child> + <child> + <object class="GtkBox" id="create_account_box"> + <property name="orientation">vertical</property> + <property name="margin">20</property> + <property name="margin-start">50</property> + <property name="margin-end">50</property> + <property name="visible">True</property> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Create Account</property> + <property name="margin-bottom">20</property> + <property name="visible">True</property> + <attributes> + <attribute name="scale" value="1.3"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Choose a public server</property> + <attributes> + <attribute name="weight" value="PANGO_WEIGHT_BOLD"/> + </attributes> + </object> + </child> + <child> + <object class="GtkFrame"> + <property name="visible">True</property> + <child> + <object class="GtkScrolledWindow"> + <property name="max_content_height">300</property> + <property name="propagate_natural_height">True</property> + <property name="hscrollbar_policy">never</property> + <property name="visible">True</property> + <child> + <object class="GtkListBox" id="server_list_box"> + <property name="visible">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="margin-top">20</property> + <property name="label" translatable="yes">Or specify a server address</property> + <attributes> + <attribute name="weight" value="PANGO_WEIGHT_BOLD"/> + </attributes> + </object> + </child> + <child> + <object class="GtkEntry" id="server_entry"> + <property name="activates_default">True</property> + <property name="can_default">True</property> + <property name="visible">True</property> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="margin-top">30</property> + <property name="orientation">horizontal</property> + <child> + <object class="GtkButton" id="login_button"> + <property name="label" translatable="yes">Sign in instead</property> + <property name="visible">True</property> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + <child> + <object class="GtkButton" id="select_server_continue"> + <property name="sensitive">False</property> + <property name="can_default">True</property> + <property name="visible">True</property> + <style> + <class name="text-button"/> + <class name="suggested-action"/> + </style> + <child> + <object class="GtkStack" id="select_server_continue_stack"> + <property name="visible">True</property> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Next</property> + <property name="visible">True</property> + </object> + <packing> + <property name="name">label</property> + </packing> + </child> + <child> + <object class="GtkSpinner"> + <property name="active">True</property> + <property name="visible">True</property> + </object> + <packing> + <property name="name">spinner</property> + </packing> + </child> + </object> + + </child> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="name">server</property> + </packing> + </child> + <child> + <object class="GtkBox" id="register_box"> + <property name="margin">20</property> + <property name="margin-start">50</property> + <property name="margin-end">50</property> + <property name="orientation">vertical</property> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="register_title"> + <property name="margin-bottom">10</property> + <property name="visible">True</property> + <attributes> + <attribute name="scale" value="1.3"/> + </attributes> + </object> + </child> + <child> + <object class="GtkBox" id="form_box"> + <property name="orientation">vertical</property> + <property name="visible">True</property> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="margin-top">30</property> + <property name="orientation">horizontal</property> + <child> + <object class="GtkButton" id="register_form_back"> + <property name="label" translatable="yes">Pick another server</property> + <property name="visible">True</property> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + <child> + <object class="GtkButton" id="register_form_continue"> + <property name="can_default">True</property> + <property name="visible">True</property> + <style> + <class name="text-button"/> + <class name="suggested-action"/> + </style> + <child> + <object class="GtkStack" id="register_form_continue_stack"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="register_form_continue_label"> + <property name="label" translatable="yes">Next</property> + <property name="visible">True</property> + </object> + <packing> + <property name="name">label</property> + </packing> + </child> + <child> + <object class="GtkSpinner"> + <property name="active">True</property> + <property name="visible">True</property> + </object> + <packing> + <property name="name">spinner</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="name">form</property> + </packing> + </child> + </object> + </child> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - <property name="width">1</property> - <property name="height">1</property> + <property name="index">-1</property> </packing> </child> - <child> - <object class="GtkEntry" id="password_entry"> - <property name="activates_default">True</property> - <property name="hexpand">True</property> - <property name="input_purpose">password</property> - <property name="width_request">200</property> + <child type="overlay"> + <object class="GtkRevealer" id="notification_revealer"> <property name="visible">True</property> - <property name="visibility">False</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">start</property> + <child> + <object class="GtkFrame" id="frame2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <style> + <class name="app-notification"/> + </style> + <child> + <object class="GtkBox" id="box2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">20</property> + <child> + <object class="GtkLabel" id="notification_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <child type="label_item"> + <placeholder/> + </child> + </object> + </child> </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="yes">Local alias</property> - <property name="xalign">1</property> - <property name="visible">True</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="alias_entry"> - <property name="activates_default">True</property> - <property name="hexpand">True</property> - <property name="width_request">200</property> - <property name="visible">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> </child> </object> </child> </object> </child> - <action-widgets> - <action-widget response="cancel">cancel_button</action-widget> - <action-widget response="ok" default="true">ok_button</action-widget> - </action-widgets> </template> </interface> 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<ListBoxRow, string> list_box_jids = new HashMap<ListBoxRow, string>(); + 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(@"<a href=\"$(form.oob)\">$(form.oob)</a>") { 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(@"<a href=\"http://$(server)\">$(server)</a>")) { 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<StanzaNode> 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<string> 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<Module> IDENTITY = new ModuleIdentity<Module>(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"); + } +} + +} |