diff --git a/Cargo.toml b/Cargo.toml index 175c415..fe86615 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ winapi = { version = "0.3.9", features = ["winuser", "psapi"] } base64 = "0.21" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +dirs = "4.0" [profile.test] env = { "RUST_LOG" = "debug" } diff --git a/info.json b/info.json index c13ff43..c10ae6b 100755 --- a/info.json +++ b/info.json @@ -1,6 +1,6 @@ { "cookies": "", - "magic": "3", + "magic": "2022-10-20 11:25:09.92", "password": "ZWtrb2Jhb19fX19fX19fXzlytojwgd3qVW7PNYs7Y9s=", "user_name": "ekko.bao" } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 577787e..858d832 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,9 @@ -use log::Log; use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; -use rand::{random, Rng}; +use rand::Rng; use reqwest::header::HeaderMap; use reqwest::{Client, Response}; use scraper::{Html, Selector}; -use core::ascii; use std::error::Error; use std::io::{self, Write}; use std::time::Duration; @@ -14,10 +12,14 @@ use std::sync::Arc; use tokio::{task, time}; use std::collections::BTreeMap; extern crate log; -use serde_json::{self, to_string, Value}; +use serde_json::{self, Value}; use serde::Serialize; -use std::fs::File; +use std::fs::{self, File}; use std::io::BufReader; +use dirs; +use std::path::Path; +use std::time::UNIX_EPOCH; +use std::io::Read; pub mod wclip; pub mod sys_res; @@ -126,6 +128,8 @@ pub struct ClipboardSync { ip: String, clip: wclip::Wclip, cfg: Config, + pipe: MsgSyncPipe, + characters: Character, } const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.42"; @@ -140,43 +144,163 @@ pub struct ClipboardMsgs { #[derive(Debug)] enum MsgSyncState { - None, - ForceInVM, - NeedSync, + Idle, + FocusOnVM, + WaitingSendMsg, + WaitingRecMsg, } +#[derive(Clone)] +pub enum Character { + Producer, + Consumer, +} +struct MsgSyncPipe { + pub pipe_file: String, + atime: Duration, +} + +/* + * 这里使用一个共享的pipe文件的atime来同步消息状态, + * 当消费者请求消息时,将尝试访问pipe文件,更新访问时间。使得生产者知道此时应该要发送消息了。 + * 生产者尝试访问pipe文件发现atime被更新,说明此时有消费者请求发送消息,那么就发送消息并将atime更新,使得消费者知道此时有一条消息更新了, + */ +impl MsgSyncPipe { + pub fn new(c:&Character) -> Result> { + let pipe_file = match c { + Character::Producer => Self::get_producer_pipe()?, + Character::Consumer => Self::get_consumer_pipe()?, + }; + let atime = Self::get_pipe_atime(&pipe_file); + Ok(MsgSyncPipe { + pipe_file, + atime, + }) + } + + fn get_pipe_atime(path:&str) -> Duration { + let metadata = fs::metadata(path).unwrap(); + // 获取最后访问时间 + if let Ok(accessed) = metadata.accessed() { + accessed.duration_since(UNIX_EPOCH).unwrap() + } else { + Duration::new(0, 0) + } + } + + pub fn get_update(&mut self) -> bool { + let atime = Self::get_pipe_atime(&self.pipe_file); + log::trace!("pipe file access time: {:?}", atime); + if atime > self.atime { + self.atime = atime; + return true; + } + return false; + } + + pub fn set_update(&mut self) { + // read pipe file to update access time + // let mut file = File::options().read(true).open(&self.pipe_file).unwrap(); + // let mut buffer = Vec::new(); + // log::trace!("try update pipe file access time"); + // let _ = file.read_to_end(&mut buffer); + let mut file = File::options().write(true).open(&self.pipe_file).unwrap(); + let _ = file.write("sstar_l0l3clip_sync".as_bytes()); + drop(file); + self.get_update(); + log::trace!("pipe file access time updated: {:?}", self.atime); + } + + fn get_consumer_pipe() -> Result> { + let path = match dirs::home_dir() { + Some(path) => path, + None => return Err("can not get user HOME dir".into()), + }; + let path = path.join(".sstar_l0l3clip_sync"); + if !path.exists() { + let mut file = fs::OpenOptions::new().create(true).write(true).open(&path)?; + let _ = file.write("sstar_l0l3clip_sync".as_bytes()); + } + Ok(path.to_str().unwrap().to_string()) + } + + fn get_producer_pipe() -> Result> { + let path = match dirs::home_dir() { + Some(path) => path, + None => return Err("can not get home dir".into()), + }; + let path = path.join(".sstar_l0l3clip_sync").to_str().unwrap().to_string(); + let mut path = path.split(':').into_iter(); + let _ = path.next(); //ignore driver letter + let sub_path = path.next().unwrap().to_string(); + for drive in 'C'..='Z' { + let sub_path = format!("{}:{}", drive, sub_path); + let sub_path = sub_path.replace("baoyucang", "BYC10"); + log::trace!("trying to find pipe file in {}", sub_path); + if Path::new(&sub_path).exists() { + return Ok(sub_path); + } + } + return Err("You should mapping l0 c:\\ to l3".into()) + } +} +static VM_EXE_NAME:&str = "mstsc.exe"; // 同步获取消息的线程 -async fn msg_sync(ctx:Arc>) { - let VM_EXE_NAME = "Code.exe".to_string(); +async fn msg_reciver(ctx:Arc>) { let mut ctx = ctx.lock().await; - let mut sta = MsgSyncState::None; + let mut sta = MsgSyncState::Idle; let mut turn_time; - //let _ = ctx.update_msg().await; + fn on_vm() -> bool { + let vm_exe_name =VM_EXE_NAME.to_string(); + let prog_name = get_foredround_window_name(); + prog_name == vm_exe_name + } + let mut state_time = time::Instant::now(); loop{ - log::trace!("current state is {:?}", sta); + // log::trace!("current state is {:?}", sta); match sta{ - MsgSyncState::None => { + MsgSyncState::Idle => { turn_time = 200; - let prog_name = get_foredround_window_name(); - if prog_name == VM_EXE_NAME { - sta = MsgSyncState::ForceInVM; + // 如果当前前台的窗口是VM的窗口说明鼠标在VM窗口内切换到等待移出的逻辑 + if on_vm() { + sta = MsgSyncState::FocusOnVM; turn_time = 0; } else { - log::trace!("foreground prog is [{}] {}, waiting: [{}] {}", prog_name, prog_name.len(), VM_EXE_NAME, VM_EXE_NAME.len()); + log::trace!("Idle, waiting for mouse move to vm window"); } }, - MsgSyncState::ForceInVM => { + MsgSyncState::FocusOnVM => { turn_time = 100; - let prog_name = get_foredround_window_name(); - if prog_name != VM_EXE_NAME { + // 如果已经移出VM窗口,说明从L3切换到了L0,可能存在需要更新msg的情况那么就开始请求对方发送消息 + if !on_vm() { turn_time = 0; - sta = MsgSyncState::NeedSync; + //请求远端发送消息 + ctx.pipe.set_update(); + sta = MsgSyncState::WaitingSendMsg; + state_time = time::Instant::now(); + log::trace!("waiting for remote send msg..."); } }, - MsgSyncState::NeedSync => { + MsgSyncState::WaitingSendMsg => { + turn_time = 50; + // 如果这里检测到了对方更新了文件状态,说明其已经发送完毕消息,我们开始时接收。 + if ctx.pipe.get_update() { + sta = MsgSyncState::WaitingRecMsg; + turn_time = 0; + } else { + //最多等待1秒钟,如果超过1秒钟还没收到消息,那么就切换到空闲状态 + if time::Instant::now() - state_time > Duration::new(1, 0) { + sta = MsgSyncState::Idle; + turn_time = 0; + } + } + }, + MsgSyncState::WaitingRecMsg => { turn_time = 0; - let _ = ctx.update_msg().await; - sta = MsgSyncState::None; + log::trace!("now we can recv msg"); + // 接收完毕消息后切换到新的一轮判断中来 + let _ = ctx.recv_msg().await; + sta = MsgSyncState::Idle; }, } if turn_time != 0 { @@ -184,9 +308,81 @@ async fn msg_sync(ctx:Arc>) { } } } + +// 同步获取消息的线程 +async fn msg_sender(ctx:Arc>) { + let mut ctx = ctx.lock().await; + let mut sta = MsgSyncState::Idle; + let mut turn_time; + fn on_edge() -> bool { + let (width, height) = sys_res::screen_size(); + let (x,y) = sys_res::cursor_pos(); + let margin = 20; + if x < margin || y< margin || x > width - margin || y > height - margin { + return true + } + log::trace!("cursor pos:({},{})", x, y); + false + } + loop{ + // log::trace!("current state is {:?}", sta); + match sta{ + MsgSyncState::Idle => { + turn_time = 0; + // 更新下最新的文件状态,保证后续判断准确 + ctx.pipe.get_update(); // refresh pipe file status + sta = MsgSyncState::FocusOnVM; + }, + MsgSyncState::FocusOnVM => { + turn_time = 100; + // 当前处于窗口边缘,尝试切换到发送消息的逻辑:等待对方请求发送 + if on_edge() { + sta = MsgSyncState::WaitingSendMsg; + turn_time = 0; + } + }, + MsgSyncState::WaitingSendMsg => { + turn_time = 50; + // 如果对方需要发送消息那么他会更新atime,这里就尝试发送消息 + if ctx.pipe.get_update() { + let _ = ctx.clip2msg().await; + log::trace!("send msg done"); + turn_time = 0; + sta = MsgSyncState::WaitingRecMsg; + ctx.pipe.set_update();//sent ok set update flag + // 如果文件没更新说明不需要发送消息,并且鼠标也不在边缘说明重新移回VM窗口内了,可以切换到等待状态 + } else if !on_edge() { + turn_time = 0; + sta = MsgSyncState::Idle; + } + }, + MsgSyncState::WaitingRecMsg => { + turn_time = 0; + sta = MsgSyncState::Idle; + // 发送消息后鼠标还是保持在边缘说明当前鼠标还是在VM窗口外面,需要等待鼠标移动到VM窗口内,然后再发送消息 + if on_edge() { + turn_time = 100; + sta = MsgSyncState::WaitingRecMsg; + } + }, + } + if turn_time != 0 { + time::sleep(Duration::new(0, turn_time*1000*1000)).await; + } + } +} + pub async fn start_msg_sync(ctx:Arc>) -> Result, Box>{ - let handle = task::spawn(msg_sync(ctx)); - Ok(handle) + match ctx.lock().await.characters { + Character::Consumer => { + let handle = task::spawn(msg_reciver(ctx.clone())); + Ok(handle) + }, + Character::Producer => { + let handle = task::spawn(msg_sender(ctx.clone())); + Ok(handle) + }, + } } enum Msg <'a>{ @@ -197,13 +393,14 @@ enum Msg <'a>{ } impl ClipboardSync { - pub fn new(ip: &str, info_file:&str) -> Result> { - let cfg = Config::new(info_file)?; + pub fn new(ip: &str, info_file:&str, c:&Character) -> Result> { Ok(ClipboardSync { web: Client::new(), ip: ip.to_string(), clip: wclip::Wclip::new(), - cfg, + cfg: Config::new(info_file)?, + pipe: MsgSyncPipe::new(c)?, + characters: c.clone(), }) } @@ -306,7 +503,7 @@ impl ClipboardSync { Ok(resp) } - pub async fn update_msg(&mut self) -> Result<(), Box> { + pub async fn recv_msg(&mut self) -> Result<(), Box> { let resp = self.get_newest_msg().await?; self.msg2clip(&resp).await; Ok(()) @@ -394,7 +591,7 @@ impl ClipboardSync { } } - pub async fn send_msg(&mut self, msg: &str) -> Result<(), Box> { + async fn send_msg(&mut self, msg: &str) -> Result<(), Box> { let url = format!("http://{}/SSWeb/rd/copy_paste.jsp?send=y", self.ip); let mut headers = HeaderMap::new(); headers.insert("Host", "ssweb".parse().unwrap()); @@ -418,7 +615,7 @@ impl ClipboardSync { Ok(()) } - pub async fn split_msg(&mut self, msg: &str) -> Result, i32> { + async fn split_msg(&mut self, msg: &str) -> Result, i32> { let mut start = 0; let mut msgs = Vec::new(); let mut cur = 0; @@ -473,7 +670,10 @@ impl ClipboardSync { Ok(msgs) => msgs, Err(_) => return, }, - None => return, + None => { + log::info!("clipboard is not string, ignore it"); + return + }, }; //遍历所有分包并进行发送 for item in msg { @@ -487,7 +687,7 @@ async fn test_message2clip() { use std::fs::File; use std::io::Read; - let mut ins = ClipboardSync::new("127.0.0.1", "info.json").unwrap(); + let mut ins = ClipboardSync::new("127.0.0.1", "info.json", &Character::Producer).unwrap(); let mut html = String::new(); File::open("message.html").unwrap().read_to_string(&mut html).unwrap(); ins.msg2clip(&html).await; @@ -496,7 +696,7 @@ async fn test_message2clip() #[tokio::test] async fn test_start_msg_sync() { - let ins = ClipboardSync::new("127.0.0.1:5000", "info.json").unwrap(); + let ins = ClipboardSync::new("127.0.0.1:5000", "info.json", &Character::Producer).unwrap(); let ctx = Arc::new(Mutex::new(ins)); let handle = start_msg_sync(ctx.clone()).await.unwrap(); time::sleep(Duration::new(10, 0)).await; @@ -505,7 +705,7 @@ async fn test_start_msg_sync() #[tokio::test] async fn test_split_msg() { - let mut ins = ClipboardSync::new("127.0.0.1:5000", "info.json").unwrap(); + let mut ins = ClipboardSync::new("127.0.0.1:5000", "info.json", &Character::Producer).unwrap(); let raw_msg = "在这个快速发展的世界中,technology is evolving at an unprecedented rate! 我们需要不断学习和适应新的变化。#创新@未来$梦想%实现^挑战&机遇*合作+成长=成功~体验|分享<知识>探索{可能性}。Let's work together to create a better tomorrow! 生活充满了各种可能性,我们要勇敢面对每一个挑战。1234567890!@#$%^&*()_+-=<>?[]{},.。;:‘’“”()【】《》——"; let msgs = ins.split_msg(raw_msg).await.unwrap(); println!("{:?}", msgs); @@ -513,7 +713,7 @@ async fn test_split_msg() { #[tokio::test] async fn test_send_send_msg() { - let mut ins = ClipboardSync::new("127.0.0.1:5000", "info.json").unwrap(); + let mut ins = ClipboardSync::new("127.0.0.1:5000", "info.json", &Character::Producer).unwrap(); let raw_msg = "在这个快速发展的世界中,technology is evolving at an unprecedented rate! 我们需要不断学习和适应新的变化。#创新@未来$梦想%实现^挑战&机遇*合作+成长=成功~体验|分享<知识>探索{可能性}。Let's work together to create a better tomorrow! 生活充满了各种可能性,我们要勇敢面对每一个挑战。1234567890!@#$%^&*()_+-=<>?[]{},.。;:‘’“”()【】《》——"; ins.clip.set(&raw_msg); ins.clip2msg().await; diff --git a/src/main.rs b/src/main.rs index 195100d..0f92628 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,16 @@ extern crate log; -use clipboard_sync::ClipboardSync; -use std::net::IpAddr; -use trust_dns_resolver::{ - config::{ResolverConfig, ResolverOpts}, - Resolver, -}; -use tokio::sync::{Mutex}; +use clipboard_sync::{ClipboardSync, Character}; +// use std::net::IpAddr; +// use trust_dns_resolver::{ +// config::{ResolverConfig, ResolverOpts}, +// Resolver, +// }; +use tokio::sync::Mutex; use std::sync::Arc; -use clap::{App, Arg, ArgGroup}; +use clap::{App, Arg}; // const SERVER:&str = "172.19.36.79"; -const SERVER:&str = "127.0.0.1:5000"; - -enum Character { - Producer, - Consumer, -} +const SERVER:&str = "192.168.0.116:5000"; fn param_parser() -> (Character, String) { let mut character = Character::Consumer; @@ -61,11 +56,11 @@ fn param_parser() -> (Character, String) { (character, info_file.to_string()) } -async fn producer(info: &str) { - let mut ins: ClipboardSync = match ClipboardSync::new(SERVER, &info) { +async fn start(info: &str, c:&Character) { + let mut ins: ClipboardSync = match ClipboardSync::new(SERVER, &info, &c) { Ok(ins) => ins, Err(e) => { - log::error!("create syns instance fail: {}", e); + log::error!("create clipboard sync instance fail: {}", e); return; } }; @@ -81,10 +76,6 @@ async fn producer(info: &str) { let _ = handle.await; } -async fn consumer(_info: &str) { - log::warn!("TODO: support Producer in the feature") -} - #[tokio::main] async fn main() -> Result<(), Box> { env_logger::init(); @@ -107,13 +98,6 @@ async fn main() -> Result<(), Box> { // } // } let (character, info) = param_parser(); - match character{ - Character::Producer => { - producer(&info).await; - } - Character::Consumer => { - consumer(&info).await; - } - } + start(&info, &character).await; Ok(()) } diff --git a/src/sys_res/mod.rs b/src/sys_res/mod.rs index 3e37508..07809a5 100755 --- a/src/sys_res/mod.rs +++ b/src/sys_res/mod.rs @@ -46,7 +46,7 @@ pub fn get_foredround_window_rect() -> Option<(i32, i32, i32, i32)> { } pub fn get_foredround_window_name() -> String { - let mut name = String::new(); + let full_path; unsafe { let foreground_window = GetForegroundWindow(); @@ -71,11 +71,11 @@ pub fn get_foredround_window_name() -> String { // let mut class_name: [u16; 256] = [0; 256]; // GetClassNameW(foreground_window, class_name.as_mut_ptr(), 256); - name = String::from_utf16_lossy(&image_name); + full_path = String::from_utf16_lossy(&image_name); // let mut class_name_str = OsString::from_wide(&class_name); // log::info!("Class Name: {}", class_name_str.to_string_lossy().to_string()); } - let name = name.split('\\').last().unwrap().trim_end_matches('\0').to_string(); + let name = full_path.split('\\').last().unwrap().trim_end_matches('\0').to_string(); //let name = path.file_name().unwrap().to_string_lossy().to_string(); // println!("Current Proc Name: [{}]", name); name @@ -93,7 +93,7 @@ fn test_screen_size() { #[test] fn test_get_foredround_window_rect() { // env_logger::init(); - let (x, y, width, height) = get_foredround_window_rect().unwrap(); + let (_x, _y, width, height) = get_foredround_window_rect().unwrap(); // assert!(x >= 0); // assert!(y >= 0); assert!(width > 0);