From a640312503ac97132ea8bf035ccba52d0708b64d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 5 Sep 2022 13:32:11 +0200 Subject: [PATCH] feat(crypto): Add method to format emojis This patch adds a method to format a list of emojis in a a terminal friendly way. This method was borrowed from weechat-matrix but it's also quite useful in our own emoji verification example. --- crates/matrix-sdk-crypto/src/lib.rs | 4 +- .../matrix-sdk-crypto/src/verification/mod.rs | 37 +++++++++++++++++++ .../src/verification/sas/inner_sas.rs | 4 +- .../src/verification/sas/sas_state.rs | 4 +- .../src/encryption/verification/mod.rs | 2 +- .../src/encryption/verification/sas.rs | 4 +- examples/emoji_verification/src/main.rs | 9 +++-- 7 files changed, 52 insertions(+), 12 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/lib.rs b/crates/matrix-sdk-crypto/src/lib.rs index e02c3751b..a1179721b 100644 --- a/crates/matrix-sdk-crypto/src/lib.rs +++ b/crates/matrix-sdk-crypto/src/lib.rs @@ -87,7 +87,9 @@ pub use requests::{ OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest, UploadSigningKeysRequest, }; pub use store::{CrossSigningKeyExport, CryptoStoreError, SecretImportError, SecretInfo}; -pub use verification::{AcceptSettings, CancelInfo, Emoji, Sas, Verification, VerificationRequest}; +pub use verification::{ + format_emojis, AcceptSettings, CancelInfo, Emoji, Sas, Verification, VerificationRequest, +}; #[cfg(feature = "qrcode")] pub use verification::{QrVerification, ScanError}; diff --git a/crates/matrix-sdk-crypto/src/verification/mod.rs b/crates/matrix-sdk-crypto/src/verification/mod.rs index 29c411562..e15f53e41 100644 --- a/crates/matrix-sdk-crypto/src/verification/mod.rs +++ b/crates/matrix-sdk-crypto/src/verification/mod.rs @@ -83,6 +83,43 @@ pub struct Emoji { pub description: &'static str, } +/// Format the the list of emojis as a two line string. +/// +/// The first line will contain the emojis spread out so the second line can +/// contain the descriptions centered bellow the emoji. +pub fn format_emojis(emojis: [Emoji; 7]) -> String { + let (emojis, descriptions): (Vec<_>, Vec<_>) = + emojis.iter().map(|e| (e.symbol, e.description)).unzip(); + + let center_emoji = |emoji: &str| -> String { + const EMOJI_WIDTH: usize = 2; + // These are emojis that need VARIATION-SELECTOR-16 (U+FE0F) so that they are + // rendered with coloured glyphs. For these, we need to add an extra + // space after them so that they are rendered properly in terminals. + const VARIATION_SELECTOR_EMOJIS: [&str; 7] = ["☁️", "❤️", "☂️", "✏️", "✂️", "☎️", "✈️"]; + + // Hack to make terminals behave properly when one of the above is printed. + let emoji = if VARIATION_SELECTOR_EMOJIS.contains(&emoji) { + format!("{} ", emoji) + } else { + emoji.to_owned() + }; + + // This is a trick to account for the fact that emojis are wider than other + // monospace characters. + let placeholder = ".".repeat(EMOJI_WIDTH); + + format!("{:^12}", placeholder).replace(&placeholder, &emoji) + }; + + let emoji_string = emojis.iter().map(|e| center_emoji(e)).collect::>().join(""); + + let description = + descriptions.iter().map(|d| format!("{:^12}", d)).collect::>().join(""); + + format!("{emoji_string}\n{description}") +} + impl VerificationStore { pub async fn get_device( &self, diff --git a/crates/matrix-sdk-crypto/src/verification/sas/inner_sas.rs b/crates/matrix-sdk-crypto/src/verification/sas/inner_sas.rs index ef6927bb6..2c0517931 100644 --- a/crates/matrix-sdk-crypto/src/verification/sas/inner_sas.rs +++ b/crates/matrix-sdk-crypto/src/verification/sas/inner_sas.rs @@ -32,9 +32,9 @@ use crate::{ identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, verification::{ event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent}, - Cancelled, + Cancelled, Emoji, }, - Emoji, ReadOnlyAccount, ReadOnlyOwnUserIdentity, + ReadOnlyAccount, ReadOnlyOwnUserIdentity, }; #[derive(Clone, Debug)] diff --git a/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs b/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs index 6c8f5bdfe..ac72d0b35 100644 --- a/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs +++ b/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs @@ -61,9 +61,9 @@ use crate::{ AcceptContent, DoneContent, KeyContent, MacContent, OwnedAcceptContent, OwnedStartContent, StartContent, }, - Cancelled, FlowId, + Cancelled, Emoji, FlowId, }, - Emoji, ReadOnlyAccount, ReadOnlyOwnUserIdentity, + ReadOnlyAccount, ReadOnlyOwnUserIdentity, }; const KEY_AGREEMENT_PROTOCOLS: &[KeyAgreementProtocol] = diff --git a/crates/matrix-sdk/src/encryption/verification/mod.rs b/crates/matrix-sdk/src/encryption/verification/mod.rs index bf696e2dc..9e5626b94 100644 --- a/crates/matrix-sdk/src/encryption/verification/mod.rs +++ b/crates/matrix-sdk/src/encryption/verification/mod.rs @@ -35,9 +35,9 @@ mod qrcode; mod requests; mod sas; +pub use matrix_sdk_base::crypto::{format_emojis, AcceptSettings, CancelInfo, Emoji}; #[cfg(feature = "qrcode")] pub use matrix_sdk_base::crypto::{matrix_sdk_qrcode::QrVerificationData, ScanError}; -pub use matrix_sdk_base::crypto::{AcceptSettings, CancelInfo, Emoji}; #[cfg(feature = "qrcode")] pub use qrcode::QrVerification; pub use requests::VerificationRequest; diff --git a/crates/matrix-sdk/src/encryption/verification/sas.rs b/crates/matrix-sdk/src/encryption/verification/sas.rs index 2991e6c69..9618daed8 100644 --- a/crates/matrix-sdk/src/encryption/verification/sas.rs +++ b/crates/matrix-sdk/src/encryption/verification/sas.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use matrix_sdk_base::crypto::{AcceptSettings, CancelInfo, ReadOnlyDevice, Sas as BaseSas}; +use matrix_sdk_base::crypto::{AcceptSettings, CancelInfo, Emoji, ReadOnlyDevice, Sas as BaseSas}; use ruma::{events::key::verification::cancel::CancelCode, UserId}; use crate::{error::Result, Client}; @@ -151,7 +151,7 @@ impl SasVerification { /// } /// # anyhow::Ok(()) }); /// ``` - pub fn emoji(&self) -> Option<[super::Emoji; 7]> { + pub fn emoji(&self) -> Option<[Emoji; 7]> { self.inner.emoji() } diff --git a/examples/emoji_verification/src/main.rs b/examples/emoji_verification/src/main.rs index fa81219d4..d53c401b2 100644 --- a/examples/emoji_verification/src/main.rs +++ b/examples/emoji_verification/src/main.rs @@ -1,5 +1,5 @@ use std::{ - io, + io::{self, Write}, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -11,7 +11,7 @@ use clap::Parser; use matrix_sdk::{ self, config::SyncSettings, - encryption::verification::{SasVerification, Verification}, + encryption::verification::{format_emojis, SasVerification, Verification}, ruma::{ events::{ room::message::MessageType, AnySyncMessageLikeEvent, AnySyncTimelineEvent, @@ -25,9 +25,10 @@ use url::Url; async fn wait_for_confirmation(client: Client, sas: SasVerification) { let emoji = sas.emoji().expect("The emoji should be available now"); - let emoji: Vec<&str> = emoji.iter().map(|e| e.symbol).collect(); - println!("Does the emoji match: {:?}", emoji); + println!("\nDo the emojis match: \n{}", format_emojis(emoji)); + print!("Confirm with `yes` or cancel with `no`: "); + std::io::stdout().flush().expect("We should be able to flush stdout"); let mut input = String::new(); io::stdin().read_line(&mut input).expect("error: unable to read user input");