1. 完成消息的接收循环的编写,现在的逻辑是:
1. 如果当前的前台程序是xxx那么就进入等待移出状态, 2. 如果当前是等待移出状态,然后切换了前台程序到其他程序那就触发一次同步 3. 同步后进入idle状态等待聚焦到xxx 2. 完成消息的发送基础步骤的准备,自动获取剪切板数据并且根据长度进行拆包进行发送。 3. 创建一个python的flask应用方便快速测试 4. 完成命令行参数的CLI化,方便直接使用,l0就是consumer,l3就是producer 5. 完成用户名的加密解密,使用机器的硬盘序列号进行加密解密 6. 保存msg的magic和cookies,方便重启使用, 但是目前来说cookies没有也是ok的。 因为没有做cookies的过期处理还。每次启动都会登录处理,并且登录期间过期的话只会异常,不会恢复, 所以这块还需要完善一下,如果登录期间发现过期了应当重新认证一下。 7. 等待完善发送的逻辑,这块本意是想要在l3的窗口鼠标移动到边缘之后保持不动就进行发送, 但是会存在一些和l0之间时序上匹配的问题。 如果l3另外启动一个用户界面用来交给用户触发交互呢,又做不到程序起初想要的无感的想法,这块还要再确认下
This commit is contained in:
96
src/encrypt/mod.rs
Executable file
96
src/encrypt/mod.rs
Executable file
@ -0,0 +1,96 @@
|
||||
use std::error::Error;
|
||||
use std::process::Command;
|
||||
extern crate log;
|
||||
use aes::Aes128;
|
||||
use block_modes::{BlockMode, Cbc};
|
||||
use block_modes::block_padding::Pkcs7;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
type Aes128Cbc = Cbc<Aes128, Pkcs7>;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EncryptErr {
|
||||
NotBase64,
|
||||
}
|
||||
|
||||
impl fmt::Display for EncryptErr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
EncryptErr::NotBase64 => write!(f, "String is not base64"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for EncryptErr {}
|
||||
|
||||
pub fn get_key() -> String {
|
||||
let output = Command::new("wmic")
|
||||
.args(&["DISKDRIVE", "get", "serialnumber"])
|
||||
.output()
|
||||
.expect("Failed to execute command");
|
||||
|
||||
let serial = String::from_utf8_lossy(&output.stdout)
|
||||
.lines()
|
||||
.nth(1) // 获取第二行,通常是序列号
|
||||
.unwrap()
|
||||
.trim()
|
||||
.to_string();
|
||||
log::trace!("hdd serial is {}", serial);
|
||||
serial
|
||||
}
|
||||
|
||||
pub fn is_base64(encrypted_data: &str) -> bool {
|
||||
match general_purpose::STANDARD.decode(encrypted_data) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt(raw_data: &str, key: &str, iv:&str) -> String {
|
||||
let key = append_align(key);
|
||||
let iv = append_align(iv);
|
||||
|
||||
let cipher = Aes128Cbc::new_from_slices(&key, &iv).unwrap();
|
||||
|
||||
let pos = cipher.encrypt_vec(&raw_data.as_bytes());
|
||||
// println!("pos buff size is {} {}, {}", general_purpose::STANDARD.encode(&pos), pos.len(), data.len());
|
||||
let ret = [iv.to_vec(), pos.to_vec()].concat();// Prepend IV for decryption
|
||||
general_purpose::STANDARD.encode(ret)
|
||||
}
|
||||
|
||||
pub fn decrypt(encrypted_data: &str, key: &str) -> Result<String, Box<dyn Error>> {
|
||||
let key: Vec<u8> = append_align(key);
|
||||
let encrypted_data = general_purpose::STANDARD.decode(encrypted_data)?;
|
||||
let (iv, data) = encrypted_data.split_at(16); // First 16 bytes are IV
|
||||
|
||||
let cipher = Aes128Cbc::new_from_slices(&key, &iv)?;
|
||||
let decrypted_data = cipher.decrypt_vec(data).unwrap();
|
||||
|
||||
let result = String::from_utf8(decrypted_data)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
|
||||
fn append_align(uuid: &str) -> Vec<u8> {
|
||||
let mut key = uuid.as_bytes().to_vec();
|
||||
key.resize(16, b'_');
|
||||
key
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_key(){
|
||||
get_key();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt() {
|
||||
let user_name = "ekko.bao";
|
||||
let password = "PASSWORD";
|
||||
let machine_id = get_key();
|
||||
let encrypted_data = encrypt(password,&machine_id, user_name);
|
||||
// println!("encrypted_data is {}", encrypted_data);
|
||||
let ret = decrypt(&encrypted_data, &machine_id).unwrap();
|
||||
println!("decrypt is {}", ret);
|
||||
assert_eq!(password, ret);
|
||||
}
|
364
src/lib.rs
364
src/lib.rs
@ -1,34 +1,136 @@
|
||||
|
||||
use log::Log;
|
||||
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
|
||||
use rand::{random, Rng};
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::{Client, Response};
|
||||
use scraper::{Html, Selector};
|
||||
use core::ascii;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::io::{self, Write};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::Mutex;
|
||||
use std::sync::Arc;
|
||||
use tokio::{task, time};
|
||||
use std::collections::BTreeMap;
|
||||
extern crate log;
|
||||
use serde_json::{self, to_string, Value};
|
||||
use serde::Serialize;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
|
||||
pub mod wclip;
|
||||
pub mod sys_res;
|
||||
pub mod encrypt;
|
||||
|
||||
use sys_res::*;
|
||||
pub struct ClipboardSync {
|
||||
|
||||
#[derive(Debug, Serialize, Default)]
|
||||
pub struct Config {
|
||||
user_name: String,
|
||||
password: String,
|
||||
cookies: String,
|
||||
magic: String,
|
||||
|
||||
save_path: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(json_file: &str) -> Result<Config, Box<dyn Error>> {
|
||||
let file = File::open(json_file)?;
|
||||
let reader = BufReader::new(file);
|
||||
let json: Value = serde_json::from_reader(reader)?;
|
||||
let user_name = json.get("user_name").expect("not found user_name in info.json").as_str().unwrap().to_string();
|
||||
log::info!("user_name is {}", user_name);
|
||||
let mut password = json.get("password").expect("not found password in info.json").as_str().unwrap().to_string();
|
||||
let magic = match json.get("magic") {
|
||||
Some(magic) => magic.as_str().unwrap().to_string(),
|
||||
None => "~".to_string(),
|
||||
};
|
||||
let cookies = match json.get("cookies") {
|
||||
Some(cookies) => cookies.as_str().unwrap().to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
let gen_cfg = move |user_name:String, password:String|{
|
||||
let cfg = Config {
|
||||
user_name,
|
||||
password,
|
||||
cookies,
|
||||
magic,
|
||||
save_path: json_file.to_string()
|
||||
};
|
||||
cfg
|
||||
};
|
||||
drop(json);
|
||||
if encrypt::is_base64(&password) {
|
||||
let key = encrypt::get_key();
|
||||
let _ = encrypt::decrypt(&password, &key)?;
|
||||
Ok(gen_cfg(user_name, password))
|
||||
} else {
|
||||
let key = encrypt::get_key();
|
||||
password = encrypt::encrypt(&password, &key, &user_name);
|
||||
let cfg = gen_cfg(user_name, password);
|
||||
cfg.update();
|
||||
Ok(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&self) {
|
||||
let json = serde_json::json!({
|
||||
"user_name": self.user_name,
|
||||
"password": self.password,
|
||||
"cookies": self.cookies,
|
||||
"magic": self.magic,
|
||||
});
|
||||
let json = serde_json::to_string_pretty(&json).unwrap();
|
||||
let mut f = File::options().truncate(true).write(true).create(true).open(&self.save_path).unwrap();
|
||||
let _ = f.write(json.as_bytes());
|
||||
}
|
||||
|
||||
pub fn user_name(&mut self, value: Option<&str>) -> &str {
|
||||
if let Some(value) = value {
|
||||
self.user_name = value.to_string();
|
||||
self.update();
|
||||
}
|
||||
&self.user_name
|
||||
}
|
||||
pub fn password(&mut self, value: Option<&str>) -> String {
|
||||
if let Some(value) = value {
|
||||
let key = encrypt::get_key();
|
||||
self.password = encrypt::encrypt(&value, &key, &self.user_name);
|
||||
self.update();
|
||||
}
|
||||
let key = encrypt::get_key();
|
||||
encrypt::decrypt(&self.password, &key).expect("password cant decrypt")
|
||||
}
|
||||
|
||||
pub fn cookies(&mut self, value: Option<&str>) -> &str {
|
||||
if let Some(value) = value {
|
||||
self.cookies = value.to_string();
|
||||
self.update();
|
||||
}
|
||||
&self.cookies
|
||||
}
|
||||
|
||||
pub fn magic(&mut self, value: Option<&str>) -> &str {
|
||||
if let Some(value) = value {
|
||||
self.magic = value.to_string();
|
||||
self.update();
|
||||
}
|
||||
&self.magic
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClipboardSync {
|
||||
web: Client,
|
||||
ip: String,
|
||||
cookies: String,
|
||||
clip: wclip::Wclip,
|
||||
//last message magic
|
||||
cache_magic: String,
|
||||
cfg: Config,
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
fn quote(input: &String) -> String {
|
||||
fn quote(input: &str) -> String {
|
||||
percent_encode(input.as_bytes(), NON_ALPHANUMERIC).to_string()
|
||||
}
|
||||
|
||||
@ -36,14 +138,49 @@ pub struct ClipboardMsgs {
|
||||
pub msgs: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MsgSyncState {
|
||||
None,
|
||||
ForceInVM,
|
||||
NeedSync,
|
||||
}
|
||||
|
||||
// 同步获取消息的线程
|
||||
async fn msg_sync(ctx:Arc<Mutex<ClipboardSync>>){
|
||||
async fn msg_sync(ctx:Arc<Mutex<ClipboardSync>>) {
|
||||
let VM_EXE_NAME = "Code.exe".to_string();
|
||||
let mut ctx = ctx.lock().await;
|
||||
let mut sta = MsgSyncState::None;
|
||||
let mut turn_time;
|
||||
//let _ = ctx.update_msg().await;
|
||||
loop{
|
||||
let _ = ctx.update_msg().await;
|
||||
time::sleep(Duration::new(10, 0)).await;
|
||||
let prog_name = get_foredround_window_name();
|
||||
if prog_name == "VMware Horizon Client"{
|
||||
log::trace!("current state is {:?}", sta);
|
||||
match sta{
|
||||
MsgSyncState::None => {
|
||||
turn_time = 200;
|
||||
let prog_name = get_foredround_window_name();
|
||||
if prog_name == VM_EXE_NAME {
|
||||
sta = MsgSyncState::ForceInVM;
|
||||
turn_time = 0;
|
||||
} else {
|
||||
log::trace!("foreground prog is [{}] {}, waiting: [{}] {}", prog_name, prog_name.len(), VM_EXE_NAME, VM_EXE_NAME.len());
|
||||
}
|
||||
},
|
||||
MsgSyncState::ForceInVM => {
|
||||
turn_time = 100;
|
||||
let prog_name = get_foredround_window_name();
|
||||
if prog_name != VM_EXE_NAME {
|
||||
turn_time = 0;
|
||||
sta = MsgSyncState::NeedSync;
|
||||
}
|
||||
},
|
||||
MsgSyncState::NeedSync => {
|
||||
turn_time = 0;
|
||||
let _ = ctx.update_msg().await;
|
||||
sta = MsgSyncState::None;
|
||||
},
|
||||
}
|
||||
if turn_time != 0 {
|
||||
time::sleep(Duration::new(0, turn_time*1000*1000)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,26 +191,24 @@ pub async fn start_msg_sync(ctx:Arc<Mutex<ClipboardSync>>) -> Result<task::JoinH
|
||||
|
||||
enum Msg <'a>{
|
||||
// magic str, message context
|
||||
Msg((String, &'a str)),
|
||||
Msg(&'a str),
|
||||
//index of message pack, total message pack number, magic str, message context
|
||||
Sub((u8, u8, String, &'a str)),
|
||||
}
|
||||
|
||||
impl ClipboardSync {
|
||||
pub fn new(ip: &str, user_name: &str, password: &str) -> ClipboardSync {
|
||||
ClipboardSync {
|
||||
user_name: user_name.to_string(),
|
||||
password: password.to_string(),
|
||||
pub fn new(ip: &str, info_file:&str) -> Result<ClipboardSync, Box<dyn Error>> {
|
||||
let cfg = Config::new(info_file)?;
|
||||
Ok(ClipboardSync {
|
||||
web: Client::new(),
|
||||
ip: ip.to_string(),
|
||||
cookies: "".to_string(),
|
||||
clip: wclip::Wclip::new(),
|
||||
cache_magic: "0xff".to_string(),
|
||||
}
|
||||
cfg,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async fn verify(&self) -> Result<Response, Box<dyn Error>> {
|
||||
async fn verify(&mut self) -> Result<Response, Box<dyn Error>> {
|
||||
// 开始验证
|
||||
let security_check_url = format!("http://{}/SSWeb/rd/j_security_check", self.ip);
|
||||
let mut headers = HeaderMap::new();
|
||||
@ -100,11 +235,11 @@ impl ClipboardSync {
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
headers.insert("Cookie", self.cookies.parse().unwrap());
|
||||
headers.insert("Cookie", self.cfg.cookies(None).parse().unwrap());
|
||||
let contents = format!(
|
||||
"j_username={}&j_password={}",
|
||||
quote(&self.user_name),
|
||||
quote(&self.password)
|
||||
quote(self.cfg.user_name(None)),
|
||||
quote(&self.cfg.password(None))
|
||||
);
|
||||
let resp = self
|
||||
.web
|
||||
@ -124,7 +259,7 @@ impl ClipboardSync {
|
||||
session = value.split(';').next().unwrap().to_string();
|
||||
}
|
||||
} else {
|
||||
log::error!("cookies not found");
|
||||
log::warn!("cookies not found");
|
||||
}
|
||||
session
|
||||
}
|
||||
@ -132,9 +267,10 @@ impl ClipboardSync {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("User-Agent", USER_AGENT.parse().unwrap());
|
||||
let url = format!("http://{}/SSWeb/rd/copy_paste.jsp", self.ip);
|
||||
|
||||
let response = self.web.get(url).headers(headers).send().await?;
|
||||
self.cookies = ClipboardSync::get_cookies(&response);
|
||||
log::info!("cookies is {}", self.cookies);
|
||||
self.cfg.cookies(Some(&ClipboardSync::get_cookies(&response)));
|
||||
log::info!("cookies is [{}]", self.cfg.cookies(None));
|
||||
let mut resp = response.text().await?;
|
||||
log::trace!("Response: {}", resp);
|
||||
if resp.contains("loginForm") {
|
||||
@ -149,7 +285,7 @@ impl ClipboardSync {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_msg(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
pub async fn get_newest_msg(&mut self) -> Result<String, Box<dyn Error>> {
|
||||
//http://ssweb/SSWeb/rd/copy_paste.jsp?d-16544-p=1&d-16544-s=2
|
||||
//d-16544-s 2:时间从靠近现在开始排序 1:时间从最早开始排序
|
||||
//d-16544-o mesage排序,不要设置
|
||||
@ -167,6 +303,11 @@ impl ClipboardSync {
|
||||
headers.insert("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6".parse().unwrap());
|
||||
let response = self.web.get(url).headers(headers).send().await?;
|
||||
let resp = response.text().await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn update_msg(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
let resp = self.get_newest_msg().await?;
|
||||
self.msg2clip(&resp).await;
|
||||
Ok(())
|
||||
}
|
||||
@ -174,23 +315,21 @@ impl ClipboardSync {
|
||||
//解析可能存在的消息数据
|
||||
fn parse_msg<'a>(msg: &'a str) -> Msg {
|
||||
//x|x|x| -> index|total|magic|
|
||||
let head = &msg[0..7];
|
||||
let body = &msg[7..];
|
||||
let check = head.split('|').filter(|text| {
|
||||
let text = text.as_bytes();
|
||||
let bmsg = msg.as_bytes();
|
||||
let head = &bmsg[0..7];
|
||||
let check = head.split(|c| *c == b'|').filter(|text| {
|
||||
if text.len() == 1 {
|
||||
if text[0] >= b'!' && text[0] < b'|' {true} else {false}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}).map(|x|x.as_bytes()[0] - b'!').collect::<Vec<_>>();
|
||||
}).map(|x|x[0] - b'!').collect::<Vec<_>>();
|
||||
|
||||
if check.len() != 3 {
|
||||
//TODO: get magic
|
||||
return Msg::Msg(("maigic".to_string(), msg))
|
||||
return Msg::Msg(msg)
|
||||
}
|
||||
|
||||
return Msg::Sub((check[0], check[1], check[2].to_string(), body));
|
||||
return Msg::Sub((check[0], check[1], check[2].to_string(), &msg[7..]));
|
||||
}
|
||||
|
||||
//将message解析并放置到剪切板上
|
||||
@ -199,19 +338,27 @@ impl ClipboardSync {
|
||||
let document = Html::parse_document(html);
|
||||
// 所有剪切板的数据都是在code这个html lable上的
|
||||
let selector = Selector::parse("code").unwrap();
|
||||
//对于单个消息我们希望发送的时间作为magic,所以这里通过构建一个date selector获得
|
||||
let date_selector = Selector::parse("td.fixline").unwrap();
|
||||
let mut msg_map = BTreeMap::new();
|
||||
let mut magic_ref = "0xff".to_string();
|
||||
let mut is_first = true;
|
||||
let mut msg_index = 0;
|
||||
for element in document.select(&selector).into_iter() {
|
||||
//let text = element.text().collect::<Vec<_>>().concat();
|
||||
let text = element.text().collect::<String>();
|
||||
log::info!("Found: [{}]", text);
|
||||
log::trace!("Found: [{}]", text);
|
||||
let msg = ClipboardSync::parse_msg(text.as_str());
|
||||
match msg {
|
||||
//一整个消息。直接复制到剪切板即可
|
||||
//一整个完整的消息。直接复制到剪切板即可
|
||||
Msg::Msg(msg) => {
|
||||
if magic_ref == "0xff" {
|
||||
self.clip.set(msg.1);
|
||||
// 因为每个date和str都是对应的所以这里直接获取指定位置的date作为magic
|
||||
let magic = document.select(&date_selector).into_iter().nth(msg_index).unwrap().text().collect::<String>();
|
||||
if self.cfg.magic(None) != magic {
|
||||
self.clip.set(msg);
|
||||
self.cfg.magic(Some(&magic));
|
||||
log::info!("magic is {}, msg is {}", self.cfg.magic(None), msg);
|
||||
}
|
||||
break;//处理完毕。直接退出
|
||||
}
|
||||
},
|
||||
@ -220,13 +367,13 @@ impl ClipboardSync {
|
||||
magic_ref = magic;
|
||||
} else {
|
||||
if magic_ref != magic { // other message ignore
|
||||
is_first = false;
|
||||
msg_index += 1;
|
||||
continue
|
||||
}
|
||||
}
|
||||
if msg_map.len() == 0 && (
|
||||
self.cache_magic == magic_ref // message is repeated ignore it
|
||||
|| !is_first // we only process the newest msg
|
||||
self.cfg.magic(None) == magic_ref // message is repeated ignore it
|
||||
|| (msg_index != 0) // we only process the newest msg
|
||||
) {
|
||||
break;
|
||||
}
|
||||
@ -234,13 +381,140 @@ impl ClipboardSync {
|
||||
msg_map.insert(index, body.to_string());
|
||||
if msg_map.len() as u8 == total {
|
||||
let msg = msg_map.into_iter().map(|(_key, v)| v).collect::<String>();
|
||||
self.cache_magic = magic_ref; // save for next msg
|
||||
self.clip.set(&msg);
|
||||
if self.cfg.magic(None) != magic_ref {
|
||||
log::info!("magic is {}, msg is {}", self.cfg.magic(None), msg);
|
||||
self.clip.set(&msg);
|
||||
self.cfg.magic(Some(&magic_ref)); // save for next msg
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
is_first = false;
|
||||
msg_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_msg(&mut self, msg: &str) -> Result<(), Box<dyn Error>> {
|
||||
let url = format!("http://{}/SSWeb/rd/copy_paste.jsp?send=y", self.ip);
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("Host", "ssweb".parse().unwrap());
|
||||
headers.insert("Connection", "keep-alive".parse().unwrap());
|
||||
headers.insert("Cache-Control", "max-age=0".parse().unwrap());
|
||||
headers.insert("Origin", "http://ssweb".parse().unwrap());
|
||||
headers.insert("DNT", "1".parse().unwrap());
|
||||
headers.insert("Upgrade-Insecure-Requests", "1".parse().unwrap());
|
||||
headers.insert("Content-Type", "application/x-www-form-urlencoded".parse().unwrap());
|
||||
headers.insert("User-Agent", USER_AGENT.parse().unwrap());
|
||||
headers.insert("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9".parse().unwrap());
|
||||
headers.insert("Referer", "http://ssweb/SSWeb/rd/copy_paste.jsp?d-16544-p=1&d-16544-s=2".parse().unwrap());
|
||||
headers.insert("Accept-Encoding", "gzip, deflate".parse().unwrap());
|
||||
headers.insert("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6".parse().unwrap());
|
||||
headers.insert("Cookie", format!("{}", self.cfg.cookies(None)).parse().unwrap());
|
||||
|
||||
let contents = format!("message={}", msg);
|
||||
let _ = self.web.post(url).headers(headers).body(contents).send().await?;
|
||||
// let resp = response.text().await?;
|
||||
// Ok(resp)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn split_msg(&mut self, msg: &str) -> Result<Vec<String>, i32> {
|
||||
let mut start = 0;
|
||||
let mut msgs = Vec::new();
|
||||
let mut cur = 0;
|
||||
for c in msg.chars() {
|
||||
let byte_count = c.len_utf8();
|
||||
if cur - start + byte_count + 7 >= 300 {
|
||||
//超过限额300个字符,构造一个消息进行发送
|
||||
msgs.push(&msg[start..cur]);
|
||||
start = cur;
|
||||
if msgs.len() > b'|' as usize - b'!' as usize {
|
||||
log::warn!("msg is too long, ignore it");
|
||||
return Err(-1);
|
||||
}
|
||||
}
|
||||
cur += byte_count;
|
||||
}
|
||||
//如果拆包了的话。那么要把最后一点点数据打包一下,不然会丢msg
|
||||
if msgs.len() > 0 && cur != start {
|
||||
msgs.push(&msg[start..cur]);
|
||||
}
|
||||
fn gen_magic(cache: char) -> char {
|
||||
let mut rng = rand::thread_rng();
|
||||
// ASCII 范围从 '!' (33) 到 '|' (124)
|
||||
let start = b'!' as u8;
|
||||
let end = b'|' as u8;
|
||||
let mut ret = cache;
|
||||
//随机生成一个和上次不一样的值即可
|
||||
while ret == cache {
|
||||
ret = rng.gen_range(start..end) as char;
|
||||
}
|
||||
ret
|
||||
}
|
||||
let mut ret = Vec::new();
|
||||
let total = msgs.len() as u32;
|
||||
if total > 0 { //大消息切片为多个小消息
|
||||
let magic = gen_magic(self.cfg.magic(None).chars().next().unwrap());
|
||||
for index in 0..total {
|
||||
//x|x|x| -> index|total|magic|
|
||||
let msg = format!("{}|{}|{}", char::from_u32(b'0' as u32 + index).unwrap(),
|
||||
char::from_u32(b'0' as u32 + total).unwrap(), magic);
|
||||
ret.push(msg + msgs[index as usize]);
|
||||
}
|
||||
self.cfg.magic(Some(&magic.to_string()));
|
||||
} else { //单个消息
|
||||
ret.push(msg.to_string());
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
pub async fn clip2msg(&mut self) {
|
||||
let msg = match self.clip.get() {
|
||||
Some(msg) => match self.split_msg(&msg).await {
|
||||
Ok(msgs) => msgs,
|
||||
Err(_) => return,
|
||||
},
|
||||
None => return,
|
||||
};
|
||||
//遍历所有分包并进行发送
|
||||
for item in msg {
|
||||
let _ = self.send_msg(&item).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
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 html = String::new();
|
||||
File::open("message.html").unwrap().read_to_string(&mut html).unwrap();
|
||||
ins.msg2clip(&html).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_start_msg_sync()
|
||||
{
|
||||
let ins = ClipboardSync::new("127.0.0.1:5000", "info.json").unwrap();
|
||||
let ctx = Arc::new(Mutex::new(ins));
|
||||
let handle = start_msg_sync(ctx.clone()).await.unwrap();
|
||||
time::sleep(Duration::new(10, 0)).await;
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_split_msg() {
|
||||
let mut ins = ClipboardSync::new("127.0.0.1:5000", "info.json").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);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_send_msg() {
|
||||
let mut ins = ClipboardSync::new("127.0.0.1:5000", "info.json").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;
|
||||
}
|
99
src/main.rs
99
src/main.rs
@ -7,6 +7,83 @@ use trust_dns_resolver::{
|
||||
};
|
||||
use tokio::sync::{Mutex};
|
||||
use std::sync::Arc;
|
||||
use clap::{App, Arg, ArgGroup};
|
||||
|
||||
// const SERVER:&str = "172.19.36.79";
|
||||
const SERVER:&str = "127.0.0.1:5000";
|
||||
|
||||
enum Character {
|
||||
Producer,
|
||||
Consumer,
|
||||
}
|
||||
|
||||
fn param_parser() -> (Character, String) {
|
||||
let mut character = Character::Consumer;
|
||||
let matches = App::new("sstar_clipboard_sync")
|
||||
.version("1.0")
|
||||
.author("Ekko.bao")
|
||||
.about("sstar clipboard sync")
|
||||
.arg(
|
||||
Arg::with_name("producer")
|
||||
.short('p')
|
||||
.long("producer")
|
||||
.conflicts_with("consumer")
|
||||
.help("Data producer")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("consumer")
|
||||
.short('c')
|
||||
.long("consumer")
|
||||
.conflicts_with("producer")
|
||||
.help("Data consumer")
|
||||
)
|
||||
.group(clap::ArgGroup::new("Character")
|
||||
.args(&["consumer", "producer"])
|
||||
.required(true)
|
||||
.multiple(false))
|
||||
.arg(
|
||||
Arg::with_name("info")
|
||||
.short('i')
|
||||
.long("info")
|
||||
.value_name("info")
|
||||
.default_value("info.json")
|
||||
.help("The file of running info")
|
||||
)
|
||||
.get_matches();
|
||||
character = if matches.is_present("producer") {
|
||||
Character::Producer
|
||||
} else if matches.is_present("consumer") {
|
||||
Character::Consumer
|
||||
} else {
|
||||
character
|
||||
};
|
||||
let info_file = matches.value_of("info").unwrap();
|
||||
(character, info_file.to_string())
|
||||
}
|
||||
|
||||
async fn producer(info: &str) {
|
||||
let mut ins: ClipboardSync = match ClipboardSync::new(SERVER, &info) {
|
||||
Ok(ins) => ins,
|
||||
Err(e) => {
|
||||
log::error!("create syns instance fail: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match ins.login().await {
|
||||
Ok(()) => log::info!("login success"),
|
||||
Err(e) => {
|
||||
log::error!("login fail: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let ctx = Arc::new(Mutex::new(ins));
|
||||
let handle = clipboard_sync::start_msg_sync(ctx.clone()).await.unwrap();
|
||||
let _ = handle.await;
|
||||
}
|
||||
|
||||
async fn consumer(_info: &str) {
|
||||
log::warn!("TODO: support Producer in the feature")
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
@ -29,22 +106,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// println!("Error: {}", e);
|
||||
// }
|
||||
// }
|
||||
|
||||
let mut ins = ClipboardSync::new("172.19.36.79", "ekko.bao", "qwer12345;");
|
||||
match ins.login().await {
|
||||
Ok(()) => log::info!("登录成功"),
|
||||
Err(e) => {
|
||||
log::error!("登录失败: {}", e);
|
||||
let (character, info) = param_parser();
|
||||
match character{
|
||||
Character::Producer => {
|
||||
producer(&info).await;
|
||||
}
|
||||
Character::Consumer => {
|
||||
consumer(&info).await;
|
||||
}
|
||||
}
|
||||
let ctx = Arc::new(Mutex::new(ins));
|
||||
let handle = clipboard_sync::start_msg_sync(ctx.clone()).await.unwrap();
|
||||
let _ = handle.await;
|
||||
// 获取 Cookies
|
||||
//if let Some(cookies) = response.headers().get(COOKIE) {
|
||||
// println!("Cookies: {:?}", cookies);
|
||||
//} else {
|
||||
// println!("No cookies found.");
|
||||
//}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
extern crate winapi;
|
||||
extern crate log;
|
||||
use std::path::Path;
|
||||
use winapi::um::winuser::{
|
||||
GetCursorPos, GetSystemMetrics, SM_CXSCREEN, SM_CYSCREEN,GetWindowRect
|
||||
};
|
||||
@ -9,8 +8,6 @@ use winapi::shared::windef::{
|
||||
RECT,
|
||||
};
|
||||
use winapi::um::winuser::{GetForegroundWindow, GetWindowThreadProcessId};
|
||||
use std::ffi::OsString;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
use winapi::um::processthreadsapi::OpenProcess;
|
||||
use winapi::um::winnt::PROCESS_QUERY_INFORMATION;
|
||||
use winapi::um::psapi::GetProcessImageFileNameW;
|
||||
@ -42,15 +39,14 @@ pub fn get_foredround_window_rect() -> Option<(i32, i32, i32, i32)> {
|
||||
println!("GetWindowRect: {:?}", rect);
|
||||
Some((rect.left, rect.top, rect.right, rect.bottom))
|
||||
} else {
|
||||
println!("cant get foregtound window rect");
|
||||
log::error!("cant get foregtound window rect");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn get_foredround_window_name() -> String {
|
||||
let mut name = OsString::new();
|
||||
let mut name = String::new();
|
||||
unsafe {
|
||||
let foreground_window = GetForegroundWindow();
|
||||
|
||||
@ -62,24 +58,26 @@ pub fn get_foredround_window_name() -> String {
|
||||
|
||||
let process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, process_id);
|
||||
if process_handle.is_null() {
|
||||
panic!("Cant open foreground process with {}!", process_id)
|
||||
log::error!("Cant open foreground process with {}!", process_id);
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
let mut image_name = [0u16; 1024];
|
||||
if GetProcessImageFileNameW(process_handle, image_name.as_mut_ptr() as *mut _, image_name.len() as u32) == 0 {
|
||||
panic!("Cant Get foreground prog name!")
|
||||
log::error!("Cant Get foreground prog name!");
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
// let mut class_name: [u16; 256] = [0; 256];
|
||||
// GetClassNameW(foreground_window, class_name.as_mut_ptr(), 256);
|
||||
|
||||
name = OsString::from_wide(&image_name);
|
||||
name = 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 path = Path::new(&name);
|
||||
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
||||
println!("Current Proc Name: [{}]", name);
|
||||
let name = name.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
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user