Cargo.toml (区分部分)
[[bin]]
name = "copy_paths_client"
path = "src/client.rs"
[[bin]]
name = "copy_paths_server"
path = "src/server.rs"
[dependencies]
clipboard = "0.5.0"
这是客户端(client.rs)
use std::io::Write;
use std::net::TcpStream;
use std::process::Command;
use std::thread;
use std::time::Duration;
use std::env;
const PORT: u16 = 5038;
const SERVER_EXE: &str = "copy_paths_server.exe";
fn start_server() {
let current_exe = env::current_exe().expect("获取当前 exe 路径失败");
let server_path = current_exe.with_file_name(SERVER_EXE);
let _ = Command::new(server_path).spawn();
}
fn send_path(path: &str) -> Result<(), String> {
let mut retries = 0;
loop {
match TcpStream::connect(("127.0.0.1", PORT)) {
Ok(mut stream) => {
// 发送路径,换行作为结束符
let line = format!("{}\n", path);
stream.write_all(line.as_bytes()).map_err(|e| e.to_string())?;
stream.flush().map_err(|e| e.to_string())?;
return Ok(());
}
Err(e) if e.kind() == std::io::ErrorKind::ConnectionRefused => {
// 服务端未运行,启动它
start_server();
thread::sleep(Duration::from_millis(50));
retries += 1;
if retries > 20 {
return Err("无法连接服务端".to_string());
}
continue;
}
Err(e) => return Err(format!("连接失败: {}", e)),
}
}
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
return;
}
let path = &args[1];
if let Err(e) = send_path(path) {
eprintln!("发送失败: {}", e);
}
}
这是服务端(server.rs)
#![windows_subsystem = "windows"]
use clipboard::{ClipboardContext, ClipboardProvider};
use std::io::{BufRead, BufReader};
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::{Duration, Instant};
const PORT: u16 = 5038;
const TIMEOUT_MS: u64 = 400; // 无新消息等待 多文件选择 能稳定执行
// 两者放在同一目录
// 共享状态
struct SharedState {
paths: Vec<String>,
deadline: Instant,
idle: bool, // 可用于调试,但不用于唤醒判断
}
// 类型别名便于传递
type StateArc = Arc<(Mutex<SharedState>, Condvar)>;
fn copy_to_clipboard(paths: &[String]) {
if paths.is_empty() {
return;
}
let content = paths.join("\r\n");
if let Ok(mut ctx) = ClipboardContext::new() {
let _ = ctx.set_contents(content);
println!("已复制 {} 个路径", paths.len());
}
}
fn handle_client(stream:TcpStream, state: StateArc){
let reader = BufReader::new(stream);
let (lock, cvar) = &*state;
for line in reader.lines() {
match line {
Ok(path) if !path.is_empty() => {
let mut guard =lock.lock().unwrap();
guard.paths.push(path);
guard.deadline = Instant::now() + Duration::from_millis(TIMEOUT_MS);
guard.idle = false;
cvar.notify_one(); // 无条件调用,,
}
_ => break,
}
}
}
fn main() {
let listener = match TcpListener::bind(("127.0.0.1", PORT)) {
Ok(l) => l,
Err(e) => {
// 端口已被占用,说明已有服务端在运行,本进程直接退出
eprintln!("端口 {} 已被占用,服务端已存在: {}", PORT, e);
return;
}
};
println!("服务端启动,监听端口 {}", PORT);
let state = Arc::new((Mutex::new(SharedState {
paths: Vec::new(),
deadline: Instant::now() + Duration::from_millis(TIMEOUT_MS),
idle:true,
}), Condvar::new()));
// 超时检查线程
thread::spawn({
let state = state.clone();
move || {
let (lock, cvar) = &*state;
loop {
let mut guard = lock.lock().unwrap();
//计算需要等待的时间
let now: Instant = Instant::now();
// 安全计算等待时间(避免 deadline < now 时 panic)
if let Some(wait_time) = guard.deadline.checked_duration_since(now) {
let result = cvar.wait_timeout(guard, wait_time).unwrap();
guard = result.0;
//若因超时 返回 (未收到通知)
if result.1.timed_out() {
if !guard.paths.is_empty(){
copy_to_clipboard(&guard.paths);
guard.paths.clear();
}
guard.idle = true;
guard.deadline = Instant::now() + Duration::from_secs(1_000_000);
}
} else {
// dealine 已过 (可能刚被唤醒 且时间已到)
if !guard.paths.is_empty(){
copy_to_clipboard(&guard.paths);
guard.paths.clear();
}
guard.idle = true;
guard.deadline = Instant::now() + Duration::from_secs(1_000_000);
}
}
}});
// 主循环接受客户端连接
for stream in listener.incoming() {
match stream {
Ok(stream) => {
let state = state.clone();
thread::spawn(move || handle_client(stream, state));
}
Err(e) => eprintln!("接受连接失败: {}", e),
}
}
}