diff --git a/libs/clipboard/src/context_send.rs b/libs/clipboard/src/context_send.rs
new file mode 100644
index 000000000..869e8a11b
--- /dev/null
+++ b/libs/clipboard/src/context_send.rs
@@ -0,0 +1,60 @@
+use crate::cliprdr::*;
+use hbb_common::log;
+use std::sync::Mutex;
+
+#[cfg(windows)]
+lazy_static::lazy_static! {
+    static ref CONTEXT_SEND: ContextSend = ContextSend{addr: Mutex::new(0)};
+}
+
+pub struct ContextSend {
+    addr: Mutex<u64>,
+}
+
+impl ContextSend {
+    pub fn is_enabled() -> bool {
+        *CONTEXT_SEND.addr.lock().unwrap() != 0
+    }
+
+    pub fn enable(enabled: bool) {
+        let mut lock = CONTEXT_SEND.addr.lock().unwrap();
+        if enabled {
+            if *lock == 0 {
+                match crate::create_cliprdr_context(true, false, crate::ProcessSide::ClientSide) {
+                    Ok(context) => {
+                        log::info!("clipboard context for file transfer created.");
+                        *lock = Box::into_raw(context) as _;
+                    }
+                    Err(err) => {
+                        log::error!(
+                            "Create clipboard context for file transfer: {}",
+                            err.to_string()
+                        );
+                    }
+                }
+            }
+        } else {
+            if *lock != 0 {
+                unsafe {
+                    let _ = Box::from_raw(*lock as *mut CliprdrClientContext);
+                }
+                log::info!("clipboard context for file transfer destroyed.");
+                *lock = 0;
+            }
+        }
+    }
+
+    pub fn proc<F: FnOnce(&mut Box<CliprdrClientContext>) -> u32>(f: F) -> u32 {
+        let mut lock = CONTEXT_SEND.addr.lock().unwrap();
+        if *lock != 0 {
+            unsafe {
+                let mut context = Box::from_raw(*lock as *mut CliprdrClientContext);
+                let res = f(&mut context);
+                *lock = Box::into_raw(context) as _;
+                res
+            }
+        } else {
+            0
+        }
+    }
+}
diff --git a/libs/clipboard/src/lib.rs b/libs/clipboard/src/lib.rs
index 33547a6f2..fa65adc37 100644
--- a/libs/clipboard/src/lib.rs
+++ b/libs/clipboard/src/lib.rs
@@ -16,6 +16,8 @@ use std::{
 };
 
 pub mod cliprdr;
+pub mod context_send;
+pub use context_send::*;
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
 #[serde(tag = "t", content = "c")]
diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs
index 5ab6e3d85..662addea5 100644
--- a/src/client/io_loop.rs
+++ b/src/client/io_loop.rs
@@ -6,6 +6,9 @@ use crate::common;
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
 use crate::common::{check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL};
 
+#[cfg(windows)]
+use clipboard::{cliprdr::CliprdrClientContext, ContextSend};
+
 use crate::ui_session_interface::{InvokeUiSession, Session};
 use crate::{client::Data, client::Interface};
 
@@ -33,11 +36,6 @@ use std::collections::HashMap;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::{Arc, Mutex};
 
-#[cfg(windows)]
-lazy_static::lazy_static! {
-    static ref CLIPBOARD_FILE_CONTEXT: Mutex<u64> = Mutex::new(0);
-}
-
 pub struct Remote<T: InvokeUiSession> {
     handler: Session<T>,
     video_sender: MediaSender,
@@ -58,37 +56,6 @@ pub struct Remote<T: InvokeUiSession> {
     video_format: CodecFormat,
 }
 
-#[cfg(windows)]
-fn check_clipboard_file_context(enable_file_transfer: bool) {
-    let enabled = SERVER_FILE_TRANSFER_ENABLED.load(Ordering::SeqCst) && enable_file_transfer;
-    let mut lock = CLIPBOARD_FILE_CONTEXT.lock().unwrap();
-    if enabled {
-        if *lock == 0 {
-            match clipboard::create_cliprdr_context(true, false, clipboard::ProcessSide::ClientSide)
-            {
-                Ok(context) => {
-                    log::info!("clipboard context for file transfer created.");
-                    *lock = Box::into_raw(context) as _;
-                }
-                Err(err) => {
-                    log::error!(
-                        "Create clipboard context for file transfer: {}",
-                        err.to_string()
-                    );
-                }
-            }
-        }
-    } else {
-        if *lock != 0 {
-            unsafe {
-                let _ = Box::from_raw(*lock as *mut clipboard::cliprdr::CliprdrClientContext);
-            }
-            log::info!("clipboard context for file transfer destroyed.");
-            *lock = 0;
-        }
-    }
-}
-
 impl<T: InvokeUiSession> Remote<T> {
     pub fn new(
         handler: Session<T>,
@@ -1202,23 +1169,19 @@ impl<T: InvokeUiSession> Remote<T> {
     fn check_clipboard_file_context(&self) {
         #[cfg(windows)]
         {
-            check_clipboard_file_context(self.handler.lc.read().unwrap().enable_file_transfer);
+            let enabled = SERVER_FILE_TRANSFER_ENABLED.load(Ordering::SeqCst)
+                && self.handler.lc.read().unwrap().enable_file_transfer;
+            ContextSend::enable(enabled);
         }
     }
 
     #[cfg(windows)]
     fn handle_cliprdr_msg(&self, clip: message_proto::Cliprdr) {
         if !self.handler.lc.read().unwrap().disable_clipboard {
-            let mut lock = CLIPBOARD_FILE_CONTEXT.lock().unwrap();
-            if *lock != 0 {
-                unsafe {
-                    let mut context =
-                        Box::from_raw(*lock as *mut clipboard::cliprdr::CliprdrClientContext);
-                    if let Some(clip) = crate::clipboard_file::msg_2_clip(clip) {
-                        clipboard::server_clip_file(&mut context, self.client_conn_id, clip);
-                    }
-                    *lock = Box::into_raw(context) as _;
-                }
+            if let Some(clip) = crate::clipboard_file::msg_2_clip(clip) {
+                ContextSend::proc(|context: &mut Box<CliprdrClientContext>| -> u32 {
+                    clipboard::server_clip_file(context, self.client_conn_id, clip)
+                });
             }
         }
     }
diff --git a/src/ui.rs b/src/ui.rs
index 6a9837a59..548bfad43 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -53,11 +53,8 @@ fn check_connect_status(
 ) {
     let status = Arc::new(Mutex::new((0, false, 0, "".to_owned())));
     let options = Arc::new(Mutex::new(Config::get_options()));
-    let cloned = status.clone();
-    let cloned_options = options.clone();
     let (tx, rx) = mpsc::unbounded_channel::<ipc::Data>();
     let password = Arc::new(Mutex::new(String::default()));
-    let cloned_password = password.clone();
     std::thread::spawn(move || crate::ui_interface::check_connect_status_(reconnect, rx));
     (status, options, tx, password)
 }
diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs
index 79152abe6..9c45ad269 100644
--- a/src/ui_cm_interface.rs
+++ b/src/ui_cm_interface.rs
@@ -11,9 +11,7 @@ use std::{
 };
 
 #[cfg(windows)]
-use clipboard::empty_clipboard;
-#[cfg(windows)]
-use hbb_common::chrono::Duration;
+use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, ContextSend};
 use serde_derive::Serialize;
 
 use crate::ipc::{self, new_listener, Connection, Data};
@@ -56,7 +54,6 @@ pub struct Client {
 struct IpcTaskRunner<T: InvokeUiCM> {
     stream: Connection,
     cm: ConnectionManager<T>,
-    tx_file: mpsc::UnboundedSender<ClipboardFileData>,
     tx: mpsc::UnboundedSender<Data>,
     rx: mpsc::UnboundedReceiver<Data>,
     conn_id: i32,
@@ -233,14 +230,31 @@ pub fn get_clients_length() -> usize {
     clients.len()
 }
 
-#[derive(Debug)]
-pub enum ClipboardFileData {
-    #[cfg(windows)]
-    Clip((i32, ipc::ClipbaordFile)),
-    Enable((i32, bool)),
-}
-
 impl<T: InvokeUiCM> IpcTaskRunner<T> {
+    #[cfg(windows)]
+    async fn enable_cliprdr_file_context(&mut self, conn_id: i32, enabled: bool) {
+        if conn_id == 0 {
+            return;
+        }
+
+        let pre_enabled = ContextSend::is_enabled();
+        ContextSend::enable(enabled);
+        if !pre_enabled && ContextSend::is_enabled() {
+            allow_err!(
+                self.stream
+                    .send(&Data::ClipbaordFile(clipboard::ClipbaordFile::MonitorReady))
+                    .await
+            );
+        }
+        clipboard::set_conn_enabled(conn_id, enabled);
+        if !enabled {
+            ContextSend::proc(|context: &mut Box<CliprdrClientContext>| -> u32 {
+                clipboard::empty_clipboard(context, conn_id);
+                0
+            });
+        }
+    }
+
     async fn run(&mut self) {
         use hbb_common::config::LocalConfig;
 
@@ -251,10 +265,8 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
 
         #[cfg(windows)]
         if self.conn_id > 0 {
-            allow_err!(self.tx_file.send(ClipboardFileData::Enable((
-                self.conn_id,
-                self.file_transfer_enabled
-            ))));
+            self.enable_cliprdr_file_context(self.conn_id, self.file_transfer_enabled)
+                .await;
         }
 
         #[cfg(windows)]
@@ -297,13 +309,15 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
                                     break;
                                 }
                                 Data::Close => {
-                                    allow_err!(self.tx_file.send(ClipboardFileData::Enable((self.conn_id, false))));
+                                    #[cfg(windows)]
+                                    self.enable_cliprdr_file_context(self.conn_id, false).await;
                                     log::info!("cm ipc connection closed from connection request");
                                     break;
                                 }
                                 Data::Disconnected => {
                                     close = false;
-                                    allow_err!(self.tx_file.send(ClipboardFileData::Enable((self.conn_id, false))));
+                                    #[cfg(windows)]
+                                    self.enable_cliprdr_file_context(self.conn_id, false).await;
                                     log::info!("cm ipc connection disconnect");
                                     break;
                                 }
@@ -320,13 +334,20 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
                                 Data::FS(fs) => {
                                     handle_fs(fs, &mut write_jobs, &self.tx).await;
                                 }
-                                #[cfg(windows)]
                                 Data::ClipbaordFile(_clip) => {
-                                    allow_err!(self.tx_file.send(ClipboardFileData::Clip((self.conn_id, _clip))));
+                                    #[cfg(windows)]
+                                    {
+                                        let conn_id = self.conn_id;
+
+                                        ContextSend::proc(|context: &mut Box<CliprdrClientContext>| -> u32 {
+                                            clipboard::server_clip_file(context, conn_id, _clip)
+                                        });
+                                    }
                                 }
                                 #[cfg(windows)]
-                                Data::ClipboardFileEnabled(enabled) => {
-                                    allow_err!(self.tx_file.send(ClipboardFileData::Enable((self.conn_id, enabled))));
+                                Data::ClipboardFileEnabled(_enabled) => {
+                                    #[cfg(windows)]
+                                    self.enable_cliprdr_file_context(self.conn_id, _enabled).await;
                                 }
                                 Data::Theme(dark) => {
                                     self.cm.change_theme(dark);
@@ -364,16 +385,11 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
         }
     }
 
-    async fn ipc_task(
-        stream: Connection,
-        cm: ConnectionManager<T>,
-        tx_file: mpsc::UnboundedSender<ClipboardFileData>,
-    ) {
+    async fn ipc_task(stream: Connection, cm: ConnectionManager<T>) {
         let (tx, rx) = mpsc::unbounded_channel::<Data>();
         let mut task_runner = Self {
             stream,
             cm,
-            tx_file,
             tx,
             rx,
             conn_id: 0,
@@ -391,13 +407,6 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
 #[tokio::main(flavor = "current_thread")]
 pub async fn start_ipc<T: InvokeUiCM>(cm: ConnectionManager<T>) {
-    #[cfg(windows)]
-    let cm_clip = cm.clone();
-
-    let (tx_file, _rx_file) = mpsc::unbounded_channel::<ClipboardFileData>();
-    #[cfg(windows)]
-    std::thread::spawn(move || start_clipboard_file(_rx_file));
-
     #[cfg(windows)]
     std::thread::spawn(move || {
         log::info!("try create privacy mode window");
@@ -422,7 +431,6 @@ pub async fn start_ipc<T: InvokeUiCM>(cm: ConnectionManager<T>) {
                         tokio::spawn(IpcTaskRunner::<T>::ipc_task(
                             Connection::new(stream),
                             cm.clone(),
-                            tx_file.clone(),
                         ));
                     }
                     Err(err) => {
@@ -723,47 +731,3 @@ fn send_raw(msg: Message, tx: &UnboundedSender<Data>) {
         err => allow_err!(err),
     }
 }
-
-#[cfg(windows)]
-#[tokio::main(flavor = "current_thread")]
-pub async fn start_clipboard_file(mut rx: mpsc::UnboundedReceiver<ClipboardFileData>) {
-    let mut cliprdr_context = None;
-
-    loop {
-        tokio::select! {
-            server_msg = rx.recv() => match server_msg {
-                Some(ClipboardFileData::Clip((conn_id, clip))) => {
-                    if let Some(ctx) = cliprdr_context.as_mut() {
-                        clipboard::server_clip_file(ctx, conn_id, clip);
-                    }
-                }
-                Some(ClipboardFileData::Enable((id, enabled))) => {
-                    if enabled && cliprdr_context.is_none() {
-                        cliprdr_context = Some(match clipboard::create_cliprdr_context(true, false, clipboard::ProcessSide::ServerSide) {
-                            Ok(context) => {
-                                log::info!("clipboard context for file transfer created.");
-                                context
-                            }
-                            Err(err) => {
-                                log::error!(
-                                    "Create clipboard context for file transfer: {}",
-                                    err.to_string()
-                                );
-                                return;
-                            }
-                        });
-                    }
-                    clipboard::set_conn_enabled(id, enabled);
-                    if !enabled {
-                        if let Some(ctx) = cliprdr_context.as_mut() {
-                            clipboard::empty_clipboard(ctx, id);
-                        }
-                    }
-                }
-                None => {
-                    break
-                }
-            }
-        }
-    }
-}
diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs
index f95a743c2..a085e9822 100644
--- a/src/ui_session_interface.rs
+++ b/src/ui_session_interface.rs
@@ -6,6 +6,7 @@ use crate::client::{
     load_config, send_mouse, start_video_audio_threads, FileManager, Key, LoginConfigHandler,
     QualityStatus, KEY_MAP, SERVER_KEYBOARD_ENABLED,
 };
+#[cfg(target_os = "linux")]
 use crate::common::IS_X11;
 use crate::{client::Data, client::Interface};
 use async_trait::async_trait;
@@ -812,6 +813,7 @@ impl<T: InvokeUiSession> Session<T> {
         let keycode: u32 = keycode as u32;
         let scancode: u32 = scancode as u32;
 
+        #[cfg(not(target_os = "windows"))]
         let key = rdev::key_from_scancode(scancode) as RdevKey;
         // Windows requires special handling
         #[cfg(target_os = "windows")]