diff --git a/src/session/view/content/room_history/message_toolbar/mod.rs b/src/session/view/content/room_history/message_toolbar/mod.rs index 07096af3..9536129f 100644 --- a/src/session/view/content/room_history/message_toolbar/mod.rs +++ b/src/session/view/content/room_history/message_toolbar/mod.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, fmt::Write}; use adw::{prelude::*, subclass::prelude::*}; -use futures_util::{future, pin_mut, StreamExt}; +use futures_util::{future, lock::Mutex, pin_mut, StreamExt}; use gettextrs::{gettext, pgettext}; use gtk::{ gdk, gio, @@ -72,7 +72,7 @@ mod imp { /// The room to send messages in. #[property(get, set = Self::set_room, explicit_notify, nullable)] pub room: glib::WeakRef, - pub can_send_message_handler: RefCell>, + pub send_message_permission_handler: RefCell>, /// Whether outgoing messages should be interpreted as markdown. #[property(get, set)] pub markdown_enabled: Cell, @@ -94,6 +94,8 @@ mod imp { /// /// The fallback composer state has the `None` key. pub composer_states: RefCell, + /// A guard to avoid sending several messages at once. + pub(super) send_guard: Mutex<()>, } #[glib::object_subclass] @@ -160,7 +162,7 @@ mod imp { #[weak] obj, move |entry| { - if !obj.imp().can_send_message() { + if !obj.imp().can_compose_message() { return; } @@ -253,7 +255,7 @@ mod imp { self.completion.unparent(); if let Some(room) = self.room.upgrade() { - if let Some(handler) = self.can_send_message_handler.take() { + if let Some(handler) = self.send_message_permission_handler.take() { room.permissions().disconnect(handler); } } @@ -273,43 +275,47 @@ mod imp { let obj = self.obj(); if let Some(room) = &old_room { - if let Some(handler) = self.can_send_message_handler.take() { + if let Some(handler) = self.send_message_permission_handler.take() { room.permissions().disconnect(handler); } } if let Some(room) = &room { - let can_send_message_handler = + let send_message_permission_handler = room.permissions().connect_can_send_message_notify(clone!( #[weak(rename_to = imp)] self, move |_| { - imp.can_send_message_updated(); + imp.send_message_permission_updated(); } )); - self.can_send_message_handler - .replace(Some(can_send_message_handler)); + self.send_message_permission_handler + .replace(Some(send_message_permission_handler)); } self.room.set(room.as_ref()); - self.can_send_message_updated(); + self.send_message_permission_updated(); self.message_entry.grab_focus(); obj.notify_room(); self.update_current_composer_state(old_room.as_ref()); } - /// Whether our own user can send a message in the current room. - pub(super) fn can_send_message(&self) -> bool { + /// Whether the user can compose a message. + /// + /// It depends on whether our own user has the permission to send a + /// message in the current room. + pub(super) fn can_compose_message(&self) -> bool { self.room .upgrade() .is_some_and(|r| r.permissions().can_send_message()) } - /// Update whether our own user can send a message in the current room. - fn can_send_message_updated(&self) { - let page = if self.can_send_message() { + /// Handle an update of the permission to send a message in the current + /// room. + fn send_message_permission_updated(&self) { + let page = if self.can_compose_message() { "enabled" } else { "disabled" @@ -462,7 +468,12 @@ impl MessageToolbar { /// Add a mention of the given member to the message composer. pub fn mention_member(&self, member: &Member) { - let view = &*self.imp().message_entry; + let imp = self.imp(); + if !imp.can_compose_message() { + return; + } + + let view = &imp.message_entry; let buffer = view.buffer(); let mut insert = buffer.iter_at_mark(&buffer.get_insert()); @@ -476,7 +487,7 @@ impl MessageToolbar { /// Set the event to reply to. pub fn set_reply_to(&self, event: Event) { let imp = self.imp(); - if !imp.can_send_message() { + if !imp.can_compose_message() { return; } @@ -494,7 +505,7 @@ impl MessageToolbar { /// Set the event to edit. pub fn set_edit(&self, event: Event) { let imp = self.imp(); - if !imp.can_send_message() { + if !imp.can_compose_message() { return; } @@ -518,7 +529,10 @@ impl MessageToolbar { /// Send the text message that is currently in the message entry. async fn send_text_message(&self) { let imp = self.imp(); - if !imp.can_send_message() { + let Some(_send_guard) = imp.send_guard.try_lock() else { + return; + }; + if !imp.can_compose_message() { return; } let Some(room) = self.room() else { @@ -655,7 +669,7 @@ impl MessageToolbar { /// Open the emoji chooser in the message entry. fn open_emoji(&self) { let imp = self.imp(); - if !imp.can_send_message() { + if !imp.can_compose_message() { return; } imp.message_entry.emit_insert_emoji(); @@ -666,7 +680,11 @@ impl MessageToolbar { /// Shows a preview of the location first and asks the user to confirm the /// action. async fn send_location(&self) { - if !self.imp().can_send_message() { + let imp = self.imp(); + let Some(_send_guard) = imp.send_guard.try_lock() else { + return; + }; + if !self.imp().can_compose_message() { return; } let Some(room) = self.room() else { @@ -822,7 +840,11 @@ impl MessageToolbar { /// Shows a preview of the image first and asks the user to confirm the /// action. async fn send_image(&self, image: gdk::Texture) { - if !self.imp().can_send_message() { + let imp = self.imp(); + let Some(_send_guard) = imp.send_guard.try_lock() else { + return; + }; + if !imp.can_compose_message() { return; } @@ -849,7 +871,11 @@ impl MessageToolbar { /// Select a file to send. pub async fn select_file(&self) { - if !self.imp().can_send_message() { + let imp = self.imp(); + let Some(_send_guard) = imp.send_guard.try_lock() else { + return; + }; + if !imp.can_compose_message() { return; } @@ -864,7 +890,7 @@ impl MessageToolbar { .await { Ok(file) => { - self.send_file(file).await; + self.send_file_inner(file).await; } Err(error) => { if error.matches(gtk::DialogError::Dismissed) { @@ -882,10 +908,18 @@ impl MessageToolbar { /// Shows a preview of the file first, if possible, and asks the user to /// confirm the action. pub async fn send_file(&self, file: gio::File) { - if !self.imp().can_send_message() { + let imp = self.imp(); + let Some(_send_guard) = imp.send_guard.try_lock() else { + return; + }; + if !imp.can_compose_message() { return; } + self.send_file_inner(file).await; + } + + async fn send_file_inner(&self, file: gio::File) { let (bytes, file_info) = match load_file(&file).await { Ok(data) => data, Err(error) => { @@ -987,7 +1021,7 @@ impl MessageToolbar { /// Handle a paste action. pub fn handle_paste_action(&self) { - if !self.imp().can_send_message() { + if !self.imp().can_compose_message() { return; }