[fractal/fractal-next] login: Add auto-discovery of homeserver
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] login: Add auto-discovery of homeserver
- Date: Tue, 1 Feb 2022 13:39:12 +0000 (UTC)
commit f4611d73bbbc91b5af04ee6fe12ad64ee1acd101
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Tue Feb 1 14:24:39 2022 +0100
login: Add auto-discovery of homeserver
Also check if the url provided is a valid homeserver.
Closes #769
data/resources/assets/homeserver.svg | 62 ++++++
data/resources/resources.gresource.xml | 2 +
data/resources/style.css | 6 +-
data/resources/ui/login-advanced-dialog.ui | 32 +++
data/resources/ui/login.ui | 190 +++++++++++-----
po/POTFILES.in | 2 +
src/login.rs | 339 ++++++++++++++++++++++++++---
src/login_advanced_dialog.rs | 118 ++++++++++
src/main.rs | 1 +
src/session/mod.rs | 14 +-
10 files changed, 677 insertions(+), 89 deletions(-)
---
diff --git a/data/resources/assets/homeserver.svg b/data/resources/assets/homeserver.svg
new file mode 100644
index 000000000..4d0940cf7
--- /dev/null
+++ b/data/resources/assets/homeserver.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="340" height="200" version="1.1" viewBox="0 0 340 200" xmlns="http://www.w3.org/2000/svg"
xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <linearGradient id="linearGradient7724">
+ <stop stop-color="#bbd5f5" offset="0"/>
+ <stop stop-color="#dfecfb" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient7358" x1="-2987.1" x2="-2872.9" y1="521.67" y2="521.67"
gradientUnits="userSpaceOnUse">
+ <stop stop-color="#a51d2d" offset="0"/>
+ <stop stop-color="#ed333b" offset=".061917"/>
+ <stop stop-color="#c01c28" offset=".11006"/>
+ <stop stop-color="#c01c28" offset=".89423"/>
+ <stop stop-color="#ed333b" offset=".94682"/>
+ <stop stop-color="#a51d2d" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient7718" x1="-2960" x2="-2960" y1="613" y2="571.13"
gradientTransform="matrix(1.0132 0 0 1.0132 3866.4 -1038.4)" gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient7724"/>
+ <linearGradient id="linearGradient7739" x1="-2845" x2="-2845" y1="548" y2="513"
gradientTransform="matrix(1.0132 0 0 1.0132 3866.4 -1038.4)" gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient7724"/>
+ <linearGradient id="linearGradient7752" x1="-3020" x2="-3020" y1="538" y2="493"
gradientTransform="matrix(1.0132 0 0 1.0132 3866.4 -1038.4)" gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient7724"/>
+ <linearGradient id="linearGradient7765" x1="-2885" x2="-2885" y1="483" y2="453"
gradientTransform="matrix(1.0132 0 0 1.0132 3866.4 -1038.4)" gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient7724"/>
+ </defs>
+ <metadata>
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g transform="translate(-726.5 598.35)">
+ <g>
+ <path d="m806.58-554.07a25.329 25.329 0 0 0-25.329 25.329 25.329 25.329 0 0 0 0.0233 0.67479 20.263
20.263 0 0 0-5.0896-0.67479 20.263 20.263 0 0 0-20.263 20.263 20.263 20.263 0 0 0 20.263
20.263h151.98v-20.263h-63.058a24.063 24.063 0 0 0 2.2678-10.132 24.063 24.063 0 0 0-24.063-24.063 24.063
24.063 0 0 0-13.409 4.1061 25.329 25.329 0 0 0-23.319-15.504z" fill="url(#linearGradient7752)"/>
+ <path d="m987.94-523.68a22.796 22.796 0 0 0-20.384 12.609 17.73 17.73 0 0 0-8.9998-2.4775 17.73 17.73 0 0
0-17.533 15.198h-28.06v20.263h106.38a17.73 17.73 0 0 0 17.73-17.73 17.73 17.73 0 0 0-17.73-17.73 17.73 17.73
0 0 0-10.547 3.5045 22.796 22.796 0 0 0-20.859-13.636z" fill="url(#linearGradient7739)"/>
+ <path d="m902.83-478.08a29.762 29.762 0 0 0-29.192 24.011 22.163 22.163 0 0 0-17.667-8.8138 22.163 22.163
0 0 0-22.068 20.263h-17.192a15.198 15.198 0 0 0-15.198 15.198 15.198 15.198 0 0 0 15.198 15.198h136.78a27.862
27.862 0 0 0 27.862-27.862 27.862 27.862 0 0 0-27.862-27.862 27.862 27.862 0 0 0-22.183 11.06 29.762 29.762 0
0 0-28.476-21.192z" fill="url(#linearGradient7718)"/>
+ <path d="m953.49-584.47a20.263 20.263 0 0 0-20.263 20.263h-10.132a10.132 10.132 0 0 0-10.132 10.132
10.132 10.132 0 0 0 10.132 10.132h55.724a15.198 15.198 0 0 0 15.198-15.198 15.198 15.198 0 0 0-15.198-15.198
15.198 15.198 0 0 0-6.9319 1.688 20.263 20.263 0 0 0-18.397-11.82z" fill="url(#linearGradient7765)"/>
+ <path d="m1036.9-497.88a17.73 17.73 0 0 1-17.598 15.74h-106.38v4.0527h106.38a17.73 17.73 0 0 0
17.73-17.73 17.73 17.73 0 0 0-0.1326-2.062z" fill="#98c1f1"/>
+ <path d="m756.04-510.43c-0.0689 0.64688-0.10649 1.2967-0.11277 1.9472 0 11.191 9.0722 20.263 20.263
20.263h151.98v-4.0527h-151.98c-10.375-2e-3 -19.073-7.8392-20.151-18.158z" fill="#98c1f1"/>
+ </g>
+ <g transform="matrix(1.0132 0 0 1.0132 3866.4 -100.7)">
+ <g>
+ <path transform="translate(0 -925.48)" d="m-2930 493.29-47.457 38.617h-9.543v10.18a7.0007 7.0007 0 0 0
11.418 6.3438l6.6718-5.4297h77.82l6.6718 5.4297a7.0007 7.0007 0 0 0 11.418-6.375v-10.148h-9.543zm0 18.049
25.277 20.568h-50.555z" color="#000000" color-rendering="auto" dominant-baseline="auto"
fill="url(#linearGradient7358)" image-rendering="auto" shape-rendering="auto" solid-color="#000000"
style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal"/>
+ <rect x="-2965" y="-437.48" width="15" height="30" fill="#77767b"/>
+ <path d="m-2970-397.48 40-30 40 30v60h-80z" fill="#f6f5f4"/>
+ <path d="m-2935.8-433.48-34.219 27.996v18.09l40-32.729 40 32.729v-18.09l-34.219-27.996z" color="#000000"
color-rendering="auto" dominant-baseline="auto" fill="#c01c28" image-rendering="auto" shape-rendering="auto"
solid-color="#000000"
style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal"/>
+ </g>
+ <path d="m-2980-392.48 50-40.687 50 40.687" fill="none" stroke="#ed333b" stroke-linecap="round"
stroke-width="14"/>
+ <circle transform="scale(1,-1)" cx="-2929.5" cy="391.48" r="10" fill="#62a0ea"/>
+ <g transform="translate(15)">
+ <circle cx="-2945" cy="-357.48" r="10" fill="#77767b"/>
+ <rect x="-2955" y="-357.48" width="20" height="20" fill="#77767b"/>
+ <path transform="translate(0 -925.48)" d="m-2945 558a10 10 0 0 0-10 10v4a10 10 0 0 1 10-10 10 10 0 0 1
10 10v-4a10 10 0 0 0-10-10z" fill="#5e5c64"/>
+ </g>
+ <g>
+ <rect x="-2970" y="-342.48" width="80" height="5" fill="#241f31" opacity=".2"/>
+ <path d="m-2939.4-390.45a10 10 0 0 1-0.057-1.0352 10 10 0 0 1 10-10 10 10 0 0 1 10 10 10 10 0 0 1-0.057
0.96484 10 10 0 0 0-9.9434-8.9648 10 10 0 0 0-9.9434 9.0352z" fill="#3584e4"/>
+ <rect x="-2965" y="-437.48" width="15" height="5" fill="#3d3846"/>
+ </g>
+ </g>
+ <path d="m981.26-441.97a27.862 27.862 0 0 1-27.769 25.691h-136.78a15.198 15.198 0 0 1-15.045-13.165 15.198
15.198 0 0 0-0.15238 2.0204 15.198 15.198 0 0 0 15.198 15.198h136.78a27.862 27.862 0 0 0 27.862-27.862 27.862
27.862 0 0 0-0.0932-1.8819z" fill="#98c1f1"/>
+ <path d="m993.87-561.17a15.198 15.198 0 0 1-15.045 13.177h-55.724a10.132 10.132 0 0 1-9.922-8.0856 10.132
10.132 0 0 0-0.20973 2.0066 10.132 10.132 0 0 0 10.132 10.132h55.724a15.198 15.198 0 0 0 15.198-15.198 15.198
15.198 0 0 0-0.15238-2.0323z" fill="#98c1f1"/>
+ <title>Gnome Symbolic Icons</title>
+ </g>
+</svg>
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index fb7d7b4e4..c8d2868df 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/FractalNext/">
+ <file preprocess="xml-stripblanks">assets/homeserver.svg</file>
<file preprocess="xml-stripblanks">assets/other-device.svg</file>
<file preprocess="xml-stripblanks">assets/setup-complete.svg</file>
<file preprocess="xml-stripblanks">assets/welcome.svg</file>
@@ -52,6 +53,7 @@
<file compressed="true" preprocess="xml-stripblanks" alias="greeter.ui">ui/greeter.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="identity-verification-widget.ui">ui/identity-verification-widget.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="in-app-notification.ui">ui/in-app-notification.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="login-advanced-dialog.ui">ui/login-advanced-dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="login.ui">ui/login.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="media-viewer.ui">ui/media-viewer.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="member-menu.ui">ui/member-menu.ui</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index 331a8eabe..38dc3fa17 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -81,10 +81,14 @@ headerbar .suggested-action {
/* Login */
-.login {
+login {
min-width: 250px;
}
+login entry {
+ padding: 18px 24px;
+}
+
/* Session */
.session-loading-spinner {
diff --git a/data/resources/ui/login-advanced-dialog.ui b/data/resources/ui/login-advanced-dialog.ui
new file mode 100644
index 000000000..762d44072
--- /dev/null
+++ b/data/resources/ui/login-advanced-dialog.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="LoginAdvancedDialog" parent="AdwPreferencesWindow">
+ <property name="modal">True</property>
+ <property name="title" translatable="yes">Homeserver Discovery</property>
+ <property name="destroy-with-parent">True</property>
+ <property name="default-width">500</property>
+ <property name="default-height">300</property>
+ <property name="search-enabled">false</property>
+ <child>
+ <object class="AdwPreferencesPage">
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="description" translatable="yes">Auto-discovery, also known as "well-known
lookup", allows to discover the URL of a Matrix homeserver from a domain name. This should only be disabled
if your homeserver doesn’t support auto-discovery or if you want to provide the URL yourself.</property>
+ <child>
+ <object class="AdwActionRow">
+ <property name="title" translatable="yes">_Auto-discovery</property>
+ <property name="use-underline">true</property>
+ <child>
+ <object class="GtkSwitch">
+ <property name="valign">center</property>
+ <property name="active" bind-source="LoginAdvancedDialog" bind-property="autodiscovery"
bind-flags="sync-create|bidirectional"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/data/resources/ui/login.ui b/data/resources/ui/login.ui
index 0743eeeef..790c4bf4e 100644
--- a/data/resources/ui/login.ui
+++ b/data/resources/ui/login.ui
@@ -12,9 +12,9 @@
</object>
</property>
<child type="start">
- <object class="GtkButton">
- <property name="action_name">app.show-greeter</property>
+ <object class="GtkButton" id="back_button">
<property name="icon-name">go-previous-symbolic</property>
+ <property name="action_name">login.prev</property>
</object>
</child>
<child type="end">
@@ -34,82 +34,156 @@
<property name="vexpand">True</property>
<child>
<object class="GtkStackPage">
- <property name="name">credentials</property>
+ <property name="name">homeserver</property>
<property name="child">
<object class="AdwClamp">
- <property name="maximum-size">400</property>
- <property name="tightening-threshold">300</property>
- <property name="valign">center</property>
- <child>
+ <property name="maximum-size">360</property>
+ <property name="tightening-threshold">360</property>
+ <property name="margin-top">0</property>
+ <property name="margin-bottom">24</property>
+ <property name="margin-start">24</property>
+ <property name="margin-end">24</property>
+ <property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
- <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="spacing">24</property>
+ <child>
+ <object class="AdwClamp">
+ <property name="child">
+ <object class="GtkPicture">
+ <property
name="file">resource:///org/gnome/FractalNext/assets/homeserver.svg</property>
+ </object>
+ </property>
+ </object>
+ </child>
<child>
- <object class="GtkListBox">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
<child>
- <object class="GtkListBoxRow">
- <property name="focusable">False</property>
- <property name="selectable">False</property>
- <property name="activatable">False</property>
- <property name="child">
- <object class="GtkEntry" id="homeserver_entry">
- <property name="activates-default">True</property>
- <property name="input_purpose">GTK_INPUT_PURPOSE_URL</property>
- <property name="placeholder-text">Homeserver</property>
- <property name="margin-top">6</property>
- <property name="margin-bottom">6</property>
- <property name="margin-start">6</property>
- <property name="margin-end">6</property>
- </object>
- </property>
+ <object class="GtkEntry" id="homeserver_entry">
+ <style>
+ <class name="card"/>
+ </style>
+ <property name="activates-default">true</property>
+ <property name="secondary-icon-name">document-edit-symbolic</property>
+ <property name="secondary-icon-sensitive">false</property>
+ <property name="secondary-icon-activatable">false</property>
</object>
</child>
<child>
- <object class="GtkListBoxRow">
- <property name="focusable">False</property>
- <property name="selectable">False</property>
- <property name="activatable">False</property>
- <property name="child">
- <object class="GtkEntry" id="username_entry">
- <property name="activates-default">True</property>
- <property name="placeholder-text">Matrix Username</property>
- <property name="margin-top">6</property>
- <property name="margin-bottom">6</property>
- <property name="margin-start">6</property>
- <property name="margin-end">6</property>
- </object>
- </property>
+ <object class="GtkLabel" id="homeserver_help">
+ <style>
+ <class name="caption"/>
+ <class name="dim-label"/>
+ </style>
+ <property name="justify">left</property>
+ <property name="xalign">0.0</property>
+ <property name="margin-start">6</property>
+ <property name="margin-end">6</property>
+ <property name="wrap">true</property>
+ <property name="use-markup">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <style>
+ <class name="pill"/>
+ </style>
+ <property name="halign">center</property>
+ <property name="label">Advanced…</property>
+ <property name="action-name">login.open-advanced</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">password</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">360</property>
+ <property name="tightening-threshold">360</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">30</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel" id="password_title">
+ <style>
+ <class name="title-4"/>
+ </style>
</object>
</child>
<child>
- <object class="GtkListBoxRow">
- <property name="focusable">False</property>
- <property name="selectable">False</property>
- <property name="activatable">False</property>
- <property name="child">
- <object class="GtkPasswordEntry" id="password_entry">
- <property name="activates-default">True</property>
- <property name="show-peek-icon">True</property>
- <property name="placeholder-text">Password</property>
- <property name="margin-top">6</property>
- <property name="margin-bottom">6</property>
- <property name="margin-start">6</property>
- <property name="margin-end">6</property>
+ <object class="GtkBox">
+ <property name="spacing">6</property>
+ <property name="halign">center</property>
+ <property name="visible" bind-source="Login" bind-property="autodiscovery"
bind-flags="sync-create"/>
+ <property name="tooltip-text" translatable="yes">Homeserver URL</property>
+ <child>
+ <object class="GtkImage">
+ <property name="icon-name">user-home-symbolic</property>
</object>
- </property>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <style>
+ <class name="body"/>
+ </style>
+ <property name="label" bind-source="Login" bind-property="homeserver"
bind-flags="sync-create"/>
+ </object>
+ </child>
</object>
</child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="username_entry">
<style>
- <class name="content"/>
- <class name="login"/>
+ <class name="card"/>
</style>
+ <property name="activates-default">true</property>
+ <property name="placeholder-text" translatable="true">Matrix Username</property>
+ <property name="secondary-icon-name">document-edit-symbolic</property>
+ <property name="secondary-icon-sensitive">false</property>
+ <property name="secondary-icon-activatable">false</property>
</object>
</child>
<child>
- <object class="GtkLinkButton" id="forgot_password">
- <property name="use_underline">True</property>
- <property name="label" translatable="yes">_Forgot Password?</property>
- <property name="uri">https://app.element.io/#/forgot_password</property>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkPasswordEntry" id="password_entry">
+ <style>
+ <class name="card"/>
+ </style>
+ <property name="activates-default">True</property>
+ <property name="show-peek-icon">True</property>
+ <property name="placeholder-text" translatable="true">Password</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLinkButton" id="forgot_password">
+ <property name="use_underline">True</property>
+ <property name="label" translatable="yes">_Forgot Password?</property>
+ <property name="uri">https://app.element.io/#/forgot_password</property>
+ </object>
+ </child>
</object>
</child>
</object>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2275d5bdd..39576bb61 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -25,6 +25,7 @@ data/resources/ui/event-menu.ui
data/resources/ui/event-source-dialog.ui
data/resources/ui/greeter.ui
data/resources/ui/identity-verification-widget.ui
+data/resources/ui/login-advanced-dialog.ui
data/resources/ui/login.ui
data/resources/ui/member-menu.ui
data/resources/ui/room-creation.ui
@@ -35,6 +36,7 @@ data/resources/ui/sidebar.ui
# Rust files
src/application.rs
+src/login.rs
src/secret.rs
src/session/account_settings/devices_page/device_list.rs
src/session/account_settings/devices_page/device_row.rs
diff --git a/src/login.rs b/src/login.rs
index 8129f594d..c477ea4ca 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -1,12 +1,25 @@
-use adw::subclass::prelude::BinImpl;
-use gtk::{self, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
-use log::debug;
+use adw::{prelude::*, subclass::prelude::BinImpl};
+use gettextrs::gettext;
+use gtk::{self, glib, glib::clone, subclass::prelude::*, CompositeTemplate};
+use log::{debug, warn};
+use matrix_sdk::{
+ config::RequestConfig,
+ ruma::{
+ api::client::unversioned::get_supported_versions, identifiers::Error as IdentifierError,
+ ServerName, UserId,
+ },
+ Client, Result as MatrixResult,
+};
+use tokio::task::JoinHandle;
use url::{ParseError, Url};
-use crate::{components::SpinnerButton, Session};
+use crate::{
+ components::SpinnerButton, error::Error, login_advanced_dialog::LoginAdvancedDialog, spawn,
+ spawn_tokio, user_facing_error::UserFacingError, Session,
+};
mod imp {
- use std::cell::RefCell;
+ use std::cell::{Cell, RefCell};
use glib::{
subclass::{InitializingObject, Signal},
@@ -21,18 +34,28 @@ mod imp {
pub struct Login {
pub current_session: RefCell<Option<Session>>,
#[template_child]
+ pub back_button: TemplateChild<gtk::Button>,
+ #[template_child]
pub next_button: TemplateChild<SpinnerButton>,
#[template_child]
pub main_stack: TemplateChild<gtk::Stack>,
#[template_child]
pub homeserver_entry: TemplateChild<gtk::Entry>,
#[template_child]
+ pub homeserver_help: TemplateChild<gtk::Label>,
+ #[template_child]
+ pub password_title: TemplateChild<gtk::Label>,
+ #[template_child]
pub username_entry: TemplateChild<gtk::Entry>,
#[template_child]
pub password_entry: TemplateChild<gtk::PasswordEntry>,
pub prepared_source_id: RefCell<Option<SignalHandlerId>>,
pub logged_out_source_id: RefCell<Option<SignalHandlerId>>,
pub ready_source_id: RefCell<Option<SignalHandlerId>>,
+ /// Whether auto-discovery is enabled.
+ pub autodiscovery: Cell<bool>,
+ /// The homeserver to log into.
+ pub homeserver: RefCell<Option<Url>>,
}
#[glib::object_subclass]
@@ -43,8 +66,15 @@ mod imp {
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
+ klass.set_css_name("login");
klass.set_accessible_role(gtk::AccessibleRole::Group);
klass.install_action("login.next", None, move |widget, _, _| widget.forward());
+ klass.install_action("login.prev", None, move |widget, _, _| widget.backward());
+ klass.install_action("login.open-advanced", None, move |widget, _, _| {
+ spawn!(clone!(@weak widget => async move {
+ widget.open_advanced_dialog().await;
+ }));
+ });
}
fn instance_init(obj: &InitializingObject<Self>) {
@@ -65,17 +95,67 @@ mod imp {
SIGNALS.as_ref()
}
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![
+ glib::ParamSpecString::new(
+ "homeserver",
+ "Homeserver",
+ "The homeserver to log into",
+ None,
+ glib::ParamFlags::READABLE,
+ ),
+ glib::ParamSpecBoolean::new(
+ "autodiscovery",
+ "Auto-discovery",
+ "Whether auto-discovery is enabled",
+ true,
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT,
+ ),
+ ]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "homeserver" => obj.homeserver_pretty().to_value(),
+ "autodiscovery" => obj.autodiscovery().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "autodiscovery" => obj.set_autodiscovery(value.get().unwrap()),
+ _ => unimplemented!(),
+ }
+ }
+
fn constructed(&self, obj: &Self::Type) {
obj.action_set_enabled("login.next", false);
self.parent_constructed(obj);
+ self.main_stack
+ .connect_visible_child_notify(clone!(@weak obj => move |_|
+ obj.update_next_action()
+ ));
+ obj.update_next_action();
+
self.homeserver_entry
- .connect_changed(clone!(@weak obj => move |_| obj.enable_next_action()));
+ .connect_changed(clone!(@weak obj => move |_| obj.update_next_action()));
self.username_entry
- .connect_changed(clone!(@weak obj => move |_| obj.enable_next_action()));
+ .connect_changed(clone!(@weak obj => move |_| obj.update_next_action()));
self.password_entry
- .connect_changed(clone!(@weak obj => move |_| obj.enable_next_action()));
+ .connect_changed(clone!(@weak obj => move |_| obj.update_next_action()));
}
}
@@ -85,6 +165,7 @@ mod imp {
}
glib::wrapper! {
+ /// A widget handling the login flows.
pub struct Login(ObjectSubclass<imp::Login>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
}
@@ -94,28 +175,223 @@ impl Login {
glib::Object::new(&[]).expect("Failed to create Login")
}
- fn enable_next_action(&self) {
+ pub fn homeserver(&self) -> Option<Url> {
+ self.imp().homeserver.borrow().clone()
+ }
+
+ pub fn homeserver_pretty(&self) -> Option<String> {
+ let homeserver = self.homeserver();
+ homeserver
+ .as_ref()
+ .and_then(|url| url.as_ref().strip_suffix('/').map(ToOwned::to_owned))
+ .or_else(|| homeserver.as_ref().map(ToString::to_string))
+ }
+
+ pub fn set_homeserver(&self, homeserver: Option<Url>) {
+ let priv_ = imp::Login::from_instance(self);
+
+ if self.homeserver() == homeserver {
+ return;
+ }
+
+ priv_.homeserver.replace(homeserver);
+ self.notify("homeserver");
+ }
+
+ fn visible_child(&self) -> String {
+ let priv_ = imp::Login::from_instance(self);
+ priv_.main_stack.visible_child_name().unwrap().into()
+ }
+
+ fn set_visible_child(&self, visible_child: &str) {
+ let priv_ = imp::Login::from_instance(self);
+ priv_.main_stack.set_visible_child_name(visible_child);
+ }
+
+ fn update_next_action(&self) {
+ let priv_ = imp::Login::from_instance(self);
+ match self.visible_child().as_ref() {
+ "homeserver" => {
+ let homeserver = priv_.homeserver_entry.text();
+ let enabled = if self.autodiscovery() {
+ build_server_name(homeserver.as_str()).is_ok()
+ } else {
+ build_homeserver_url(homeserver.as_str()).is_ok()
+ };
+ self.action_set_enabled("login.next", enabled);
+ priv_.next_button.set_visible(true);
+ }
+ "password" => {
+ let username_length = priv_.username_entry.text_length();
+ let password_length = priv_.password_entry.text().len();
+ self.action_set_enabled("login.next", username_length != 0 && password_length != 0);
+ priv_.next_button.set_visible(true);
+ }
+ _ => {
+ priv_.next_button.set_visible(false);
+ }
+ }
+ }
+
+ fn forward(&self) {
+ match self.visible_child().as_ref() {
+ "homeserver" => {
+ if self.autodiscovery() {
+ self.try_autodiscovery();
+ } else {
+ self.check_homeserver();
+ }
+ }
+ "password" => self.login_with_password(),
+ _ => {}
+ }
+ }
+
+ fn backward(&self) {
+ match self.visible_child().as_ref() {
+ "password" => self.set_visible_child("homeserver"),
+ _ => {
+ self.activate_action("app.show-greeter", None).unwrap();
+ }
+ }
+ }
+
+ pub fn autodiscovery(&self) -> bool {
+ self.imp().autodiscovery.get()
+ }
+
+ fn set_autodiscovery(&self, autodiscovery: bool) {
let priv_ = self.imp();
- let homeserver = priv_.homeserver_entry.text();
- let username_length = priv_.username_entry.text_length();
- let password_length = priv_.password_entry.text().len();
-
- self.action_set_enabled(
- "login.next",
- homeserver.len() != 0
- && build_homeserver_url(homeserver.as_str()).is_ok()
- && username_length != 0
- && password_length != 0,
+
+ priv_.autodiscovery.set(autodiscovery);
+ if autodiscovery {
+ priv_
+ .homeserver_entry
+ .set_placeholder_text(Some(&gettext("Domain Name…")));
+ priv_.homeserver_help.set_markup(&gettext(
+ "The domain of your Matrix homeserver, for example gnome.org",
+ ));
+ } else {
+ priv_
+ .homeserver_entry
+ .set_placeholder_text(Some(&gettext("Homeserver URL…")));
+ priv_.homeserver_help.set_markup(&gettext("The URL of your Matrix homeserver, for example <span
segment=\"word\">https://gnome.modular.im</span>"));
+ }
+ self.update_next_action();
+ }
+
+ async fn open_advanced_dialog(&self) {
+ let dialog =
+ LoginAdvancedDialog::new(self.root().unwrap().downcast_ref::<gtk::Window>().unwrap());
+ self.bind_property("autodiscovery", &dialog, "autodiscovery")
+ .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
+ .build();
+ dialog.run_future().await;
+ }
+
+ fn try_autodiscovery(&self) {
+ let server = build_server_name(self.imp().homeserver_entry.text().as_str()).unwrap();
+ let mxid = UserId::parse_with_server_name("user", &server).unwrap();
+
+ self.freeze();
+
+ let handle = spawn_tokio!(async move { Client::new_from_user_id(&mxid).await });
+
+ spawn!(
+ glib::PRIORITY_DEFAULT_IDLE,
+ clone!(@weak self as obj => async move {
+ match handle.await.unwrap() {
+ Ok(client) => {
+ let homeserver = client.homeserver().await;
+ obj.set_homeserver(Some(homeserver));
+ obj.show_password_page();
+ }
+ Err(error) => {
+ warn!("Failed to discover homeserver: {}", error);
+ let error_string = error.to_user_facing();
+
+ obj.parent_window().append_error(&Error::new(move |_| {
+ let error_label = gtk::Label::builder()
+ .label(&error_string)
+ .wrap(true)
+ .build();
+ Some(error_label.upcast())
+ }));
+ }
+ };
+ obj.unfreeze();
+ })
);
}
- fn forward(&self) {
- self.login();
+ fn check_homeserver(&self) {
+ let homeserver = build_homeserver_url(self.imp().homeserver_entry.text().as_str()).unwrap();
+ let homeserver_clone = homeserver.clone();
+
+ self.freeze();
+
+ let handle: JoinHandle<MatrixResult<_>> = spawn_tokio!(async move {
+ let client = Client::new(homeserver_clone)?;
+ Ok(client
+ .send(
+ get_supported_versions::Request::new(),
+ Some(RequestConfig::new().disable_retry()),
+ )
+ .await?)
+ });
+
+ spawn!(
+ glib::PRIORITY_DEFAULT_IDLE,
+ clone!(@weak self as obj => async move {
+ match handle.await.unwrap() {
+ Ok(_) => {
+ obj.set_homeserver(Some(homeserver));
+ obj.show_password_page();
+ }
+ Err(error) => {
+ warn!("Failed to check homeserver: {}", error);
+ let error_string = error.to_user_facing();
+
+ obj.parent_window().append_error(&Error::new(move |_| {
+ let error_label = gtk::Label::builder()
+ .label(&error_string)
+ .wrap(true)
+ .build();
+ Some(error_label.upcast())
+ }));
+ }
+ };
+ obj.unfreeze();
+ })
+ );
}
- fn login(&self) {
+ fn show_password_page(&self) {
let priv_ = self.imp();
- let homeserver = priv_.homeserver_entry.text().to_string();
+ if self.autodiscovery() {
+ // Translators: the variable is a domain name, eg. gnome.org.
+ priv_.password_title.set_markup(&gettext!(
+ "Connecting to {}",
+ format!(
+ "<span segment=\"word\">{}</span>",
+ priv_.homeserver_entry.text()
+ )
+ ));
+ } else {
+ priv_.password_title.set_markup(&gettext!(
+ "Connecting to {}",
+ format!(
+ "<span segment=\"word\">{}</span>",
+ self.homeserver_pretty().unwrap()
+ )
+ ));
+ }
+ self.set_visible_child("password");
+ }
+
+ fn login_with_password(&self) {
+ let priv_ = self.imp();
+ let homeserver = self.homeserver().unwrap();
let username = priv_.username_entry.text().to_string();
let password = priv_.password_entry.text().to_string();
@@ -124,11 +400,7 @@ impl Login {
let session = Session::new();
self.set_handler_for_prepared_session(&session);
- session.login_with_password(
- build_homeserver_url(homeserver.as_str()).unwrap(),
- username,
- password,
- );
+ session.login_with_password(homeserver, username, password, self.autodiscovery());
priv_.current_session.replace(Some(session));
}
@@ -137,6 +409,7 @@ impl Login {
priv_.homeserver_entry.set_text("");
priv_.username_entry.set_text("");
priv_.password_entry.set_text("");
+ priv_.autodiscovery.set(true);
self.unfreeze();
self.drop_session_reference();
}
@@ -152,9 +425,9 @@ impl Login {
fn unfreeze(&self) {
let priv_ = self.imp();
- self.action_set_enabled("login.next", true);
priv_.next_button.set_loading(false);
priv_.main_stack.set_sensitive(true);
+ self.update_next_action();
}
pub fn connect_new_session<F: Fn(&Self, Session) + 'static>(
@@ -240,6 +513,14 @@ impl Default for Login {
}
}
+fn build_server_name(server: &str) -> Result<Box<ServerName>, IdentifierError> {
+ let server = server
+ .strip_prefix("http://")
+ .or_else(|| server.strip_prefix("https://"))
+ .unwrap_or(server);
+ ServerName::parse(server)
+}
+
fn build_homeserver_url(server: &str) -> Result<Url, ParseError> {
if server.starts_with("http://") || server.starts_with("https://") {
Url::parse(server)
diff --git a/src/login_advanced_dialog.rs b/src/login_advanced_dialog.rs
new file mode 100644
index 000000000..8985cb67d
--- /dev/null
+++ b/src/login_advanced_dialog.rs
@@ -0,0 +1,118 @@
+use std::cell::Cell;
+
+use adw::subclass::prelude::*;
+use gtk::{gdk, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+mod imp {
+ use glib::subclass::InitializingObject;
+ use once_cell::sync::Lazy;
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/login-advanced-dialog.ui")]
+ pub struct LoginAdvancedDialog {
+ pub autodiscovery: Cell<bool>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for LoginAdvancedDialog {
+ const NAME: &'static str = "LoginAdvancedDialog";
+ type Type = super::LoginAdvancedDialog;
+ type ParentType = adw::PreferencesWindow;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+
+ klass.add_binding_signal(
+ gdk::Key::Escape,
+ gdk::ModifierType::empty(),
+ "close-request",
+ None,
+ );
+ }
+
+ fn instance_init(obj: &InitializingObject<Self>) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for LoginAdvancedDialog {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![glib::ParamSpecBoolean::new(
+ "autodiscovery",
+ "Auto-discovery",
+ "Whether auto-discovery is enabled",
+ true,
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT,
+ )]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "autodiscovery" => obj.autodiscovery().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "autodiscovery" => obj.set_autodiscovery(value.get().unwrap()),
+ _ => unimplemented!(),
+ }
+ }
+ }
+
+ impl WidgetImpl for LoginAdvancedDialog {}
+ impl WindowImpl for LoginAdvancedDialog {}
+ impl AdwWindowImpl for LoginAdvancedDialog {}
+ impl PreferencesWindowImpl for LoginAdvancedDialog {}
+}
+
+glib::wrapper! {
+ pub struct LoginAdvancedDialog(ObjectSubclass<imp::LoginAdvancedDialog>)
+ @extends gtk::Widget, gtk::Window, adw::Window, adw::PreferencesWindow, @implements gtk::Accessible;
+}
+
+impl LoginAdvancedDialog {
+ pub fn new(window: >k::Window) -> Self {
+ glib::Object::new(&[("transient-for", window)])
+ .expect("Failed to create LoginAdvancedDialog")
+ }
+
+ pub fn autodiscovery(&self) -> bool {
+ self.imp().autodiscovery.get()
+ }
+
+ pub fn set_autodiscovery(&self, autodiscovery: bool) {
+ let priv_ = self.imp();
+
+ priv_.autodiscovery.set(autodiscovery);
+ self.notify("autodiscovery");
+ }
+
+ pub async fn run_future(&self) {
+ let (sender, receiver) = futures::channel::oneshot::channel();
+ let sender = Cell::new(Some(sender));
+
+ self.connect_close_request(move |_| {
+ if let Some(sender) = sender.take() {
+ sender.send(()).unwrap();
+ }
+ gtk::Inhibit(false)
+ });
+
+ self.show();
+ receiver.await.unwrap();
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 4f65274ac..97162920b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -12,6 +12,7 @@ mod contrib;
mod error;
mod greeter;
mod login;
+mod login_advanced_dialog;
mod secret;
mod session;
mod user_facing_error;
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 5af814037..eb91c29c3 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -284,7 +284,13 @@ impl Session {
}
}
- pub fn login_with_password(&self, homeserver: Url, username: String, password: String) {
+ pub fn login_with_password(
+ &self,
+ homeserver: Url,
+ username: String,
+ password: String,
+ use_discovery: bool,
+ ) {
self.imp().logout_on_dispose.set(true);
let mut path = glib::user_data_dir();
path.push(
@@ -307,6 +313,12 @@ impl Session {
.passphrase(passphrase.clone())
.store_path(path.clone());
+ let config = if use_discovery {
+ config.use_discovery_response()
+ } else {
+ config
+ };
+
let client = Client::new_with_config(homeserver.clone(), config).unwrap();
let response = client
.login(&username, &password, None, Some("Fractal Next"))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]