1
0
Fork 0
mirror of https://github.com/icewind1991/clipboard-sync synced 2026-06-03 10:24:06 +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

56
Cargo.lock generated
View file

@ -99,6 +99,7 @@ version = "0.1.0"
dependencies = [
"clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
@ -155,6 +156,18 @@ dependencies = [
"termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "err-derive"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
@ -595,6 +608,14 @@ dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ryu"
version = "0.2.8"
@ -628,6 +649,19 @@ dependencies = [
"core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.92"
@ -643,7 +677,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -679,7 +713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.15.36"
version = "0.15.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
@ -687,6 +721,17 @@ dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synstructure"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tempfile"
version = "3.0.8"
@ -888,6 +933,7 @@ dependencies = [
"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c"
"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a"
"checksum err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3d8ff65eb6c2fc68e76557239d16f5698fd56603925b89856d3f0f7105fd4543"
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
@ -942,17 +988,21 @@ dependencies = [
"checksum regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0b2f0808e7d7e4fb1cb07feb6ff2f4bc827938f24f8c2e6a3beb7370af544bdd"
"checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f"
"checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339"
"checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2"
"checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "32746bf0f26eab52f06af0d0aa1984f641341d06d8d673c693871da2d188c9be"
"checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e"
"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
"checksum syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)" = "8b4f551a91e2e3848aeef8751d0d4eec9489b6474c720fd4c55958d8d31a430c"
"checksum syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)" = "ec52cd796e5f01d0067225a5392e70084acc4c0013fa71d55166d38a8b307836"
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
"checksum tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7dc4738f2e68ed2855de5ac9cdbe05c9216773ecde4739b2f095002ab03a13ef"
"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e"
"checksum termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8fb22f7cde82c8220e5aeacb3258ed7ce996142c77cba193f203515e26c330"

View file

@ -19,3 +19,4 @@ serde_json = "1.0"
clipboard = "0.5"
env_logger = "0.6"
ws = {version = "0.8", features = ["native-tls"]}
err-derive = "0.1"

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: _ } => {
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 {
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,
ClipboardCommand::Set {
value: _,
session: session_name,
}, client.token());
}
None => println!("session {} not found", 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,18 +62,16 @@ 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());
handle_command(command, &mut self.sessions.borrow_mut(), &self.out);
}
Err(err) => {
println!("{}", err);
}
};
Ok(())
}
Err(_) => {
println!("invalid message: {}", msg.as_text().unwrap_or_default());
Ok(())
}
}
}
fn on_close(&mut self, _: CloseCode, _: &str) {
let mut sessions = self.sessions.borrow_mut();
@ -85,7 +95,10 @@ 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(_) => {