From 972ec8d79d02ed68938b0a5825ae7f6fccd42bc5 Mon Sep 17 00:00:00 2001 From: cc-morning <90960002+cc-morning@users.noreply.github.com> Date: Sun, 24 Oct 2021 23:47:02 +0800 Subject: [PATCH 1/4] Fix missing clipboard content --- src/server/clipboard_service.rs | 82 +++++++++++++++++++-------------- src/server/service.rs | 34 -------------- 2 files changed, 48 insertions(+), 68 deletions(-) diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index 3e94d2824..a0711c8d9 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -1,12 +1,11 @@ -use std::sync::mpsc::Sender; - -use clipboard_master::{CallbackResult, ClipboardHandler, Master}; - use super::*; pub use crate::common::{ check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL as INTERVAL, CLIPBOARD_NAME as NAME, CONTENT, }; +use clipboard_master::{CallbackResult, ClipboardHandler, Master}; +use hbb_common::ResultType; +use std::{sync, sync::mpsc::Sender, thread}; struct State { ctx: Option, @@ -31,41 +30,56 @@ impl super::service::Reset for State { } } -struct ClipHandle { - tx: Sender, -} - -impl ClipboardHandler for ClipHandle { - fn on_clipboard_change(&mut self) -> CallbackResult { - let _ = self.tx.send(true); - CallbackResult::Next - } -} - pub fn new() -> GenericService { let sp = GenericService::new(NAME, true); - sp.listen::(notify, run); + sp.run::<_>(listen::run); sp } -fn notify(tx: Sender) -> ResultType<()> { - Master::new(ClipHandle { tx }).run()?; - Ok(()) -} +mod listen { + use super::*; -fn run(sp: GenericService, state: &mut State) -> ResultType<()> { - if let Some(ctx) = state.ctx.as_mut() { - if let Some(msg) = check_clipboard(ctx, None) { - sp.send(msg); - } - sp.snapshot(|sps| { - let txt = crate::CONTENT.lock().unwrap().clone(); - if !txt.is_empty() { - let msg_out = crate::create_clipboard_msg(txt); - sps.send_shared(Arc::new(msg_out)); - } - Ok(()) - })?; + struct ClipHandle { + tx: Sender<()>, + } + + impl ClipboardHandler for ClipHandle { + fn on_clipboard_change(&mut self) -> CallbackResult { + let _ = self.tx.send(()); + CallbackResult::Next + } + } + + fn notify(tx: Sender<()>) -> ResultType<()> { + Master::new(ClipHandle { tx }).run()?; + Ok(()) + } + + pub fn run(sp: GenericService) -> ResultType<()> { + let mut state = State::default(); + let (tx, rx) = sync::mpsc::channel::<()>(); + thread::spawn(|| { + let _ = notify(tx); + }); + + while sp.ok() { + sp.snapshot(|sps| { + let txt = crate::CONTENT.lock().unwrap().clone(); + if !txt.is_empty() { + let msg_out = crate::create_clipboard_msg(txt); + sps.send_shared(Arc::new(msg_out)); + } + Ok(()) + })?; + + if let Ok(()) = rx.recv() { + if let Some(ctx) = state.ctx.as_mut() { + if let Some(msg) = check_clipboard(ctx, None) { + sp.send(msg); + } + } + } + } + Ok(()) } - Ok(()) } diff --git a/src/server/service.rs b/src/server/service.rs index 3e8c840a2..b60f5a8d4 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -1,6 +1,5 @@ use super::*; use std::{ - sync::{self, mpsc::Sender}, thread::{self, JoinHandle}, time, }; @@ -154,39 +153,6 @@ impl> ServiceTmpl { } } - pub fn listen(&self, notify: N, callback: F) - where - N: 'static + FnMut(Sender) -> ResultType<()> + Send, - F: 'static + FnMut(Self, &mut S) -> ResultType<()> + Send, - S: 'static + Default + Reset, - { - let mut notify = notify; - let mut callback = callback; - let sp = self.clone(); - let (tx, rx) = sync::mpsc::channel(); - thread::spawn(move || { - let _ = notify(tx); - }); - - let thread = thread::spawn(move || { - let mut state = S::default(); - while let Ok(changed) = rx.recv() { - if changed && sp.active() { - if sp.has_subscribes() { - if let Err(err) = callback(sp.clone(), &mut state) { - log::error!("Error of {} service: {}", sp.name(), err); - #[cfg(windows)] - crate::platform::windows::try_change_desktop(); - } - } else { - state.reset(); - } - } - } - }); - self.0.write().unwrap().handle = Some(thread); - } - pub fn repeat(&self, interval_ms: u64, callback: F) where F: 'static + FnMut(Self, &mut S) -> ResultType<()> + Send, From 5164f7691949d68befb058f38485663c2e3353d9 Mon Sep 17 00:00:00 2001 From: cc-morning <90960002+cc-morning@users.noreply.github.com> Date: Mon, 25 Oct 2021 16:25:23 +0800 Subject: [PATCH 2/4] Fix lost clipboard content again --- src/server/clipboard_service.rs | 81 +++++++++++++++++---------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index a0711c8d9..f972771c3 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -4,34 +4,25 @@ pub use crate::common::{ CONTENT, }; use clipboard_master::{CallbackResult, ClipboardHandler, Master}; -use hbb_common::ResultType; -use std::{sync, sync::mpsc::Sender, thread}; - -struct State { - ctx: Option, -} - -impl Default for State { - fn default() -> Self { - let ctx = match ClipboardContext::new() { - Ok(ctx) => Some(ctx), - Err(err) => { - log::error!("Failed to start {}: {}", NAME, err); - None - } - }; - Self { ctx } - } -} - -impl super::service::Reset for State { - fn reset(&mut self) { - *CONTENT.lock().unwrap() = Default::default(); - } -} +use hbb_common::{anyhow, ResultType}; +use std::{ + sync, + sync::mpsc::{Receiver, Sender}, + thread, + time::Duration, +}; pub fn new() -> GenericService { let sp = GenericService::new(NAME, true); + + thread::spawn(|| { + let (tx, rx) = sync::mpsc::channel(); + unsafe { + listen::RECEIVER = Some(rx); + } + let _ = listen::notify(tx); + }); + sp.run::<_>(listen::run); sp } @@ -39,6 +30,10 @@ pub fn new() -> GenericService { mod listen { use super::*; + pub(super) static mut RECEIVER: Option> = None; + static mut CTX: Option = None; + static WAIT: Duration = Duration::from_millis(1000); + struct ClipHandle { tx: Sender<()>, } @@ -50,19 +45,33 @@ mod listen { } } - fn notify(tx: Sender<()>) -> ResultType<()> { + pub fn notify(tx: Sender<()>) -> ResultType<()> { Master::new(ClipHandle { tx }).run()?; Ok(()) } pub fn run(sp: GenericService) -> ResultType<()> { - let mut state = State::default(); - let (tx, rx) = sync::mpsc::channel::<()>(); - thread::spawn(|| { - let _ = notify(tx); - }); + if unsafe { CTX.as_ref() }.is_none() { + match ClipboardContext::new() { + Ok(ctx) => unsafe { + CTX = Some(ctx); + }, + Err(err) => { + log::error!("Failed to start {}: {}", NAME, err); + return Err(anyhow::Error::from(err)); + } + }; + } while sp.ok() { + if let Ok(_) = unsafe { RECEIVER.as_ref() }.unwrap().recv_timeout(WAIT) { + if let Some(mut ctx) = unsafe { CTX.as_mut() } { + if let Some(msg) = check_clipboard(&mut ctx, None) { + sp.send(msg); + } + } + } + sp.snapshot(|sps| { let txt = crate::CONTENT.lock().unwrap().clone(); if !txt.is_empty() { @@ -71,15 +80,9 @@ mod listen { } Ok(()) })?; - - if let Ok(()) = rx.recv() { - if let Some(ctx) = state.ctx.as_mut() { - if let Some(msg) = check_clipboard(ctx, None) { - sp.send(msg); - } - } - } } + + *CONTENT.lock().unwrap() = Default::default(); Ok(()) } } From 318e3cbd7d61acc4a5bd34c7173d9e5b6abf3ac9 Mon Sep 17 00:00:00 2001 From: cc-morning <90960002+cc-morning@users.noreply.github.com> Date: Tue, 26 Oct 2021 16:59:31 +0800 Subject: [PATCH 3/4] Code Format --- src/server/clipboard_service.rs | 67 +++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index f972771c3..cc470ec7e 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -8,19 +8,22 @@ use hbb_common::{anyhow, ResultType}; use std::{ sync, sync::mpsc::{Receiver, Sender}, - thread, + thread::{self}, time::Duration, }; pub fn new() -> GenericService { let sp = GenericService::new(NAME, true); + // Listening service needs to run for a long time, + // otherwise it will cause part of the content to + // be missed during the closure of the clipboard + // (during the closure, the content copied by the + // remote machine will be missed, and the clipboard + // will not be synchronized immediately when it is + // opened again), and CONTENT will not be updated thread::spawn(|| { - let (tx, rx) = sync::mpsc::channel(); - unsafe { - listen::RECEIVER = Some(rx); - } - let _ = listen::notify(tx); + let _ = listen::notify(); }); sp.run::<_>(listen::run); @@ -30,44 +33,52 @@ pub fn new() -> GenericService { mod listen { use super::*; - pub(super) static mut RECEIVER: Option> = None; + static mut CHANNEL: Option<(Sender<()>, Receiver<()>)> = None; static mut CTX: Option = None; - static WAIT: Duration = Duration::from_millis(1000); + static WAIT: Duration = Duration::from_millis(1500); - struct ClipHandle { - tx: Sender<()>, - } + struct ClipHandle; impl ClipboardHandler for ClipHandle { fn on_clipboard_change(&mut self) -> CallbackResult { - let _ = self.tx.send(()); + if let Some((tx, _rx)) = unsafe { CHANNEL.as_ref() } { + let _ = tx.send(()); + } CallbackResult::Next } } - pub fn notify(tx: Sender<()>) -> ResultType<()> { - Master::new(ClipHandle { tx }).run()?; + pub fn notify() -> ResultType<()> { + Master::new(ClipHandle).run()?; Ok(()) } pub fn run(sp: GenericService) -> ResultType<()> { - if unsafe { CTX.as_ref() }.is_none() { - match ClipboardContext::new() { - Ok(ctx) => unsafe { - CTX = Some(ctx); - }, - Err(err) => { - log::error!("Failed to start {}: {}", NAME, err); - return Err(anyhow::Error::from(err)); - } - }; + unsafe { + if CHANNEL.is_none() { + CHANNEL = Some(sync::mpsc::channel()); + } + + if CTX.is_none() { + match ClipboardContext::new() { + Ok(ctx) => { + CTX = Some(ctx); + } + Err(err) => { + log::error!("Failed to start {}: {}", NAME, err); + return Err(anyhow::Error::from(err)); + } + }; + } } while sp.ok() { - if let Ok(_) = unsafe { RECEIVER.as_ref() }.unwrap().recv_timeout(WAIT) { - if let Some(mut ctx) = unsafe { CTX.as_mut() } { - if let Some(msg) = check_clipboard(&mut ctx, None) { - sp.send(msg); + if let Some((_tx, rx)) = unsafe { CHANNEL.as_ref() } { + if let Ok(_) = rx.recv_timeout(WAIT) { + if let Some(mut ctx) = unsafe { CTX.as_mut() } { + if let Some(msg) = check_clipboard(&mut ctx, None) { + sp.send(msg); + } } } } From 0cf2db92391ce90115a8b68b44e5d581e0963016 Mon Sep 17 00:00:00 2001 From: cc-morning <90960002+cc-morning@users.noreply.github.com> Date: Wed, 27 Oct 2021 20:02:17 +0800 Subject: [PATCH 4/4] Clipboard listener lifecycle follows service --- src/server/clipboard_service.rs | 105 +++++++++++++++++--------------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index cc470ec7e..811aebb7b 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -6,26 +6,16 @@ pub use crate::common::{ use clipboard_master::{CallbackResult, ClipboardHandler, Master}; use hbb_common::{anyhow, ResultType}; use std::{ - sync, - sync::mpsc::{Receiver, Sender}, - thread::{self}, + io, sync, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::SyncSender, + }, time::Duration, }; pub fn new() -> GenericService { let sp = GenericService::new(NAME, true); - - // Listening service needs to run for a long time, - // otherwise it will cause part of the content to - // be missed during the closure of the clipboard - // (during the closure, the content copied by the - // remote machine will be missed, and the clipboard - // will not be synchronized immediately when it is - // opened again), and CONTENT will not be updated - thread::spawn(|| { - let _ = listen::notify(); - }); - sp.run::<_>(listen::run); sp } @@ -33,53 +23,56 @@ pub fn new() -> GenericService { mod listen { use super::*; - static mut CHANNEL: Option<(Sender<()>, Receiver<()>)> = None; - static mut CTX: Option = None; + static RUNNING: AtomicBool = AtomicBool::new(true); static WAIT: Duration = Duration::from_millis(1500); - struct ClipHandle; + struct ClipHandle { + tx: SyncSender<()>, + } impl ClipboardHandler for ClipHandle { fn on_clipboard_change(&mut self) -> CallbackResult { - if let Some((tx, _rx)) = unsafe { CHANNEL.as_ref() } { - let _ = tx.send(()); + if !RUNNING.load(Ordering::SeqCst) { + return CallbackResult::Stop; } + + let _ = self.tx.send(()); CallbackResult::Next } - } - pub fn notify() -> ResultType<()> { - Master::new(ClipHandle).run()?; - Ok(()) - } - - pub fn run(sp: GenericService) -> ResultType<()> { - unsafe { - if CHANNEL.is_none() { - CHANNEL = Some(sync::mpsc::channel()); - } - - if CTX.is_none() { - match ClipboardContext::new() { - Ok(ctx) => { - CTX = Some(ctx); - } - Err(err) => { - log::error!("Failed to start {}: {}", NAME, err); - return Err(anyhow::Error::from(err)); - } - }; + fn on_clipboard_error(&mut self, error: io::Error) -> CallbackResult { + if !RUNNING.load(Ordering::SeqCst) { + CallbackResult::Stop + } else { + CallbackResult::StopWithError(error) } } + } + + #[tokio::main] + pub async fn run(sp: GenericService) -> ResultType<()> { + let mut ctx = match ClipboardContext::new() { + Ok(ctx) => ctx, + Err(err) => { + log::error!("Failed to start {}: {}", NAME, err); + return Err(anyhow::Error::from(err)); + } + }; + + if !RUNNING.load(Ordering::SeqCst) { + RUNNING.store(true, Ordering::SeqCst); + } + + let (tx, rx) = sync::mpsc::sync_channel(12); + let listener = tokio::spawn(async { + log::info!("Clipboard listener running!"); + let _ = Master::new(ClipHandle { tx }).run(); + }); while sp.ok() { - if let Some((_tx, rx)) = unsafe { CHANNEL.as_ref() } { - if let Ok(_) = rx.recv_timeout(WAIT) { - if let Some(mut ctx) = unsafe { CTX.as_mut() } { - if let Some(msg) = check_clipboard(&mut ctx, None) { - sp.send(msg); - } - } + if let Ok(_) = rx.recv_timeout(WAIT) { + if let Some(msg) = check_clipboard(&mut ctx, None) { + sp.send(msg); } } @@ -93,7 +86,21 @@ mod listen { })?; } + { + RUNNING.store(false, Ordering::SeqCst); + trigger(&mut ctx); + let _ = listener.await; + log::info!("Clipboard listener stopped!"); + } + *CONTENT.lock().unwrap() = Default::default(); Ok(()) } + + fn trigger(ctx: &mut ClipboardContext) { + let _ = match ctx.get_text() { + Ok(text) => ctx.set_text(text), + Err(_) => ctx.set_text(Default::default()), + }; + } }