diff --git a/src/client.rs b/src/client.rs
index a24c531c1..c880c2dbe 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -635,7 +635,7 @@ impl Client {
     }
 
     #[cfg(not(any(target_os = "android", target_os = "ios")))]
-    fn try_start_clipboard(_conf_tx: Option<(SessionPermissionConfig, UnboundedSender<Data>)>) {
+    fn try_start_clipboard(_conf_tx: Option<ClientClipboardContext>) {
         let mut clipboard_lock = TEXT_CLIPBOARD_STATE.lock().unwrap();
         if clipboard_lock.running {
             return;
@@ -660,11 +660,17 @@ impl Client {
 
                         if let Some(msg) = check_clipboard(&mut ctx, Some(&OLD_CLIPBOARD_TEXT)) {
                             #[cfg(feature = "flutter")]
-                            crate::flutter::send_text_clipboard_msg(msg);
+                            crate::flutter::send_text_clipboard_msg(
+                                &*OLD_CLIPBOARD_TEXT.lock().unwrap(),
+                                msg,
+                            );
                             #[cfg(not(feature = "flutter"))]
-                            if let Some((cfg, tx)) = &_conf_tx {
-                                if cfg.is_text_clipboard_required() {
-                                    let _ = tx.send(Data::Message(msg));
+                            if let Some(ctx) = &_ctx {
+                                if ctx.cfg.is_text_clipboard_required()
+                                    && *OLD_CLIPBOARD_TEXT.lock().unwrap()
+                                        != *ctx.old.lock().unwrap()
+                                {
+                                    let _ = ctx.tx.send(Data::Message(msg));
                                 }
                             }
                         }
@@ -2423,3 +2429,15 @@ fn decode_id_pk(signed: &[u8], key: &sign::PublicKey) -> ResultType<(String, [u8
         bail!("Wrong public length");
     }
 }
+
+#[cfg(feature = "flutter")]
+#[cfg(not(any(target_os = "android", target_os = "ios")))]
+pub(crate) struct ClientClipboardContext;
+
+#[cfg(not(feature = "flutter"))]
+#[cfg(not(any(target_os = "android", target_os = "ios")))]
+pub(crate) struct ClientClipboardContext {
+    pub cfg: SessionPermissionConfig,
+    pub old: Arc<Mutex<String>>,
+    pub tx: UnboundedSender<Data>,
+}
diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs
index 18a8bfe3d..735a35e3a 100644
--- a/src/client/io_loop.rs
+++ b/src/client/io_loop.rs
@@ -51,8 +51,6 @@ pub struct Remote<T: InvokeUiSession> {
     // Stop sending local audio to remote client.
     stop_voice_call_sender: Option<std::sync::mpsc::Sender<()>>,
     voice_call_request_timestamp: Option<NonZeroI64>,
-    #[cfg(not(any(target_os = "android", target_os = "ios")))]
-    old_clipboard: Arc<Mutex<String>>,
     read_jobs: Vec<fs::TransferJob>,
     write_jobs: Vec<fs::TransferJob>,
     remove_jobs: HashMap<i32, RemoveJob>,
@@ -87,8 +85,6 @@ impl<T: InvokeUiSession> Remote<T> {
             audio_sender,
             receiver,
             sender,
-            #[cfg(not(any(target_os = "android", target_os = "ios")))]
-            old_clipboard: Default::default(),
             read_jobs: Vec::new(),
             write_jobs: Vec::new(),
             remove_jobs: Default::default(),
@@ -941,10 +937,11 @@ impl<T: InvokeUiSession> Remote<T> {
                             Client::try_start_clipboard(None);
                             #[cfg(not(feature = "flutter"))]
                             #[cfg(not(any(target_os = "android", target_os = "ios")))]
-                            Client::try_start_clipboard(Some((
-                                permission_config.clone(),
-                                sender.clone(),
-                            )));
+                            Client::try_start_clipboard(Some(ClientClipboardContext {
+                                cfg: permission_config.clone(),
+                                old: self.handler.old_clipboard.clone(),
+                                tx: sender.clone(),
+                            }));
 
                             #[cfg(not(any(target_os = "android", target_os = "ios")))]
                             tokio::spawn(async move {
@@ -977,7 +974,7 @@ impl<T: InvokeUiSession> Remote<T> {
                 Some(message::Union::Clipboard(cb)) => {
                     if !self.handler.lc.read().unwrap().disable_clipboard.v {
                         #[cfg(not(any(target_os = "android", target_os = "ios")))]
-                        update_clipboard(cb, Some(&self.old_clipboard));
+                        update_clipboard(cb, Some(&self.handler.old_clipboard));
                         #[cfg(any(target_os = "android", target_os = "ios"))]
                         {
                             let content = if cb.compress {
diff --git a/src/flutter.rs b/src/flutter.rs
index 6c9ff7f37..e49f04043 100644
--- a/src/flutter.rs
+++ b/src/flutter.rs
@@ -725,9 +725,9 @@ pub fn other_sessions_running(id: &str) -> bool {
 }
 
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
-pub fn send_text_clipboard_msg(msg: Message) {
+pub fn send_text_clipboard_msg(text: &str, msg: Message) {
     for (_id, session) in SESSIONS.read().unwrap().iter() {
-        if session.is_text_clipboard_required() {
+        if session.is_text_clipboard_required() && text != *session.old_clipboard.lock().unwrap() {
             session.send(Data::Message(msg.clone()));
         }
     }
diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs
index 504982f50..7ae1d5b24 100644
--- a/src/ui_session_interface.rs
+++ b/src/ui_session_interface.rs
@@ -43,6 +43,8 @@ pub struct Session<T: InvokeUiSession> {
     pub server_keyboard_enabled: Arc<RwLock<bool>>,
     pub server_file_transfer_enabled: Arc<RwLock<bool>>,
     pub server_clipboard_enabled: Arc<RwLock<bool>>,
+    #[cfg(not(any(target_os = "android", target_os = "ios")))]
+    pub old_clipboard: Arc<Mutex<String>>,
 }
 
 #[derive(Clone)]