1
0
Fork 0
mirror of https://github.com/icewind1991/clipboard-sync synced 2026-06-03 18:34:07 +02:00
This commit is contained in:
Robin Appelman 2019-06-15 14:32:04 +02:00
commit e84fe3467b
5 changed files with 190 additions and 71 deletions

View file

@ -1,26 +1,28 @@
use clipboard::{ClipboardContext, ClipboardProvider};
use crate::common::ClipboardCommand;
use std::{thread, thread::JoinHandle, time};
use clipboard::{ClipboardContext, ClipboardProvider};
use std::convert::TryFrom;
use std::env;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::{thread, thread::JoinHandle};
use ws::{connect, Message, Sender};
mod common;
fn handle_command(command: ClipboardCommand, ctx: &mut ClipboardContext, current_clipboard: Arc<Mutex<String>>) {
match command {
ClipboardCommand::Set { value, session: _ } => {
let mut clip = current_clipboard.lock().unwrap();
if *clip != value {
let _ = ctx.set_contents(value.clone());
*clip = value;
}
fn handle_command(
command: ClipboardCommand,
ctx: &mut ClipboardContext,
current_clipboard: Arc<Mutex<String>>,
) {
if let ClipboardCommand::Set { value, session: _ } = command {
let mut clip = current_clipboard.lock().unwrap();
if *clip != value {
let _ = ctx.set_contents(value.clone());
*clip = value;
}
_ => {}
}
}
fn main() {
env_logger::init();
let args: Vec<_> = env::args().collect();
@ -41,41 +43,52 @@ fn main() {
move |msg: Message| {
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
let result: serde_json::Result<ClipboardCommand> = serde_json::from_str(msg.as_text().unwrap_or_default());
match result {
Ok(command) => handle_command(command, &mut ctx, current_clipboard.clone()),
Err(_) => {}
if let Ok(command) = ClipboardCommand::try_from(msg) {
handle_command(command, &mut ctx, current_clipboard.clone());
}
Ok(())
}
}).unwrap();
})
.unwrap();
}
fn clipboard_thread(session: String, out: Sender, current_clipboard: Arc<Mutex<String>>) -> JoinHandle<()> {
const HUNDRED_MS: Duration = Duration::from_millis(100);
fn clipboard_thread(
session: String,
out: Sender,
current_clipboard: Arc<Mutex<String>>,
) -> JoinHandle<()> {
thread::spawn(move || {
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
let hundred_millis = time::Duration::from_millis(100);
{
let mut clip = current_clipboard.lock().unwrap();
*clip = ctx.get_contents().unwrap_or_default();
}
thread::sleep(hundred_millis);
thread::sleep(HUNDRED_MS);
// we need to do the listen after returning the closure for the websocket
// thus we can't send this message in the factory
send_to_server(&out, &ClipboardCommand::Listen {
session: session.clone()
});
send_to_server(
&out,
&ClipboardCommand::Listen {
session: session.clone(),
},
);
loop {
thread::sleep(hundred_millis);
thread::sleep(HUNDRED_MS);
let new_clipboard = ctx.get_contents().unwrap_or_default();
let mut clip = current_clipboard.lock().unwrap();
if *clip != new_clipboard {
send_to_server(&out, &ClipboardCommand::Set {
session: session.clone(),
value: new_clipboard.clone(),
});
send_to_server(
&out,
&ClipboardCommand::Set {
session: session.clone(),
value: new_clipboard.clone(),
},
);
*clip = new_clipboard;
}
}
@ -83,6 +96,5 @@ fn clipboard_thread(session: String, out: Sender, current_clipboard: Arc<Mutex<S
}
fn send_to_server(out: &Sender, command: &ClipboardCommand) {
let command_text = serde_json::to_string(command).unwrap();
out.send(Message::from(command_text.clone())).ok();
}
let _ = out.send(command);
}

View file

@ -1,10 +1,53 @@
use serde::{Serialize, Deserialize};
use err_derive::Error;
use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_string, Error as SerdeError};
use std::convert::TryFrom;
use ws::{Error as WsError, ErrorKind, Message};
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum ClipboardCommand {
#[serde(rename = "listen")]
Listen { session: String },
#[serde(rename = "set")]
Set { session: String, value: String }
}
Set { session: String, value: String },
}
#[derive(Debug, Error)]
pub enum ParseError {
#[error(display = "Invalid message encoding")]
Encoding,
#[error(display = "Invalid formatted message: {}", _1)]
InvalidMessage(#[error(cause)] SerdeError, String),
#[error(display = "Unknown error")]
Unknown,
}
impl From<WsError> for ParseError {
fn from(from: WsError) -> Self {
match from.kind {
ErrorKind::Encoding(_) => ParseError::Encoding,
_ => ParseError::Unknown,
}
}
}
impl TryFrom<Message> for ClipboardCommand {
type Error = ParseError;
fn try_from(msg: Message) -> Result<Self, Self::Error> {
let text = msg.as_text()?;
from_str::<ClipboardCommand>(text)
.map_err(|err| ParseError::InvalidMessage(err, text.into()))
}
}
impl From<ClipboardCommand> for Message {
fn from(command: ClipboardCommand) -> Self {
Message::from(&command)
}
}
impl From<&ClipboardCommand> for Message {
fn from(command: &ClipboardCommand) -> Self {
Message::from(to_string(command).unwrap())
}
}

View file

@ -2,34 +2,47 @@ use crate::common::ClipboardCommand;
use mio::Token;
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::rc::Rc;
use ws::{CloseCode, Error, Handler, listen, Message, Result, Sender};
use ws::{listen, CloseCode, Error, Handler, Message, Result, Sender};
mod common;
#[derive(Default)]
struct Session {
clients: HashMap<Token, Sender>
clients: HashMap<Token, Sender>,
}
fn handle_command(command: ClipboardCommand, sessions: &mut HashMap<String, Session>, client: Sender) {
match command {
ClipboardCommand::Listen { session: session_name } => {
sessions.entry(session_name).or_insert_with(|| Session {
clients: HashMap::new()
}).clients.insert(client.token(), client);
impl Session {
pub fn join(&mut self, client: Sender) {
self.clients.insert(client.token(), client);
}
}
fn handle_command(
command: ClipboardCommand,
sessions: &mut HashMap<String, Session>,
client: &Sender,
) {
match &command {
ClipboardCommand::Listen {
session: session_name,
} => {
sessions
.entry(session_name.clone())
.or_default()
.join(client.clone());
}
ClipboardCommand::Set { value, session: session_name } => {
match sessions.get_mut(&session_name) {
Some(session) => {
send_to_session(session, &ClipboardCommand::Set {
value,
session: session_name,
}, client.token());
}
None => println!("session {} not found", session_name)
ClipboardCommand::Set {
value: _,
session: session_name,
} => match sessions.get_mut(session_name) {
Some(session) => {
send_to_session(session, &command, client.token());
}
}
None => println!("session {} not found", session_name),
},
}
}
@ -37,12 +50,11 @@ fn send_to_session(session: &Session, command: &ClipboardCommand, exclude: Token
let command_text = serde_json::to_string(command).unwrap();
for client in session.clients.values() {
if client.token() != exclude {
client.send(Message::from(command_text.clone())).ok();
let _ = client.send(command_text.as_str());
}
}
}
struct Server {
out: Sender,
sessions: Rc<RefCell<HashMap<String, Session>>>,
@ -50,17 +62,15 @@ struct Server {
impl Handler for Server {
fn on_message(&mut self, msg: Message) -> Result<()> {
let result: serde_json::Result<ClipboardCommand> = serde_json::from_str(msg.as_text().unwrap_or_default());
match result {
match ClipboardCommand::try_from(msg) {
Ok(command) => {
handle_command(command, &mut self.sessions.borrow_mut(), self.out.clone());
Ok(())
handle_command(command, &mut self.sessions.borrow_mut(), &self.out);
}
Err(_) => {
println!("invalid message: {}", msg.as_text().unwrap_or_default());
Ok(())
Err(err) => {
println!("{}", err);
}
}
};
Ok(())
}
fn on_close(&mut self, _: CloseCode, _: &str) {
@ -85,11 +95,14 @@ fn main() {
let sessions: Rc<RefCell<HashMap<String, Session>>> = Rc::new(RefCell::new(HashMap::new()));
let result = listen(listen_address, |out| { Server { out, sessions: sessions.clone() } });
let result = listen(listen_address, |out| Server {
out,
sessions: sessions.clone(),
});
match result {
Ok(_) => {}
Err(_) => {
println!("error while listening");
}
}
}
}