Browse Source

login: Add auto-discovery of homeserver

Also check if the url provided is a valid homeserver.

Closes #769
merge-requests/1327/merge
Kévin Commaille 4 years ago
parent
commit
f4611d73bb
No known key found for this signature in database
GPG Key ID: DD507DAE96E8245C
  1. 62
      data/resources/assets/homeserver.svg
  2. 2
      data/resources/resources.gresource.xml
  3. 6
      data/resources/style.css
  4. 32
      data/resources/ui/login-advanced-dialog.ui
  5. 190
      data/resources/ui/login.ui
  6. 2
      po/POTFILES.in
  7. 339
      src/login.rs
  8. 118
      src/login_advanced_dialog.rs
  9. 1
      src/main.rs
  10. 14
      src/session/mod.rs

62
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>

After

Width:  |  Height:  |  Size: 6.7 KiB

2
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>

6
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 {

32
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>

190
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>

2
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

339
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)

118
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: &gtk::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();
}
}

1
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;

14
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"))

Loading…
Cancel
Save