吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 826|回复: 8
收起左侧

[学习记录] rust 版实现 - Windows右键【复制多行文件完整路径】

[复制链接]
pyjiujiu 发表于 2026-4-12 22:23

原帖是 www.52pojie.cn/thread-2101392-1-1.html
原作者已经介绍了 详细的 windows 注册表的设置,这不再涉及这部分
简单套用,替换 rust 版即可  (CopyMultiPath.exe  -换成-> copy_paths_client.exe)(#copy_paths_server.exe 也要放在同目录)

# 介绍

编译后,是两个 copy_paths_client.execopy_paths_server.exe
(码有不对的地方,欢迎指出 交流)

用的是 TCP 的协议
winodws 选中多个文件 --> 每个文件启动一个客户端进程(-client.exe + 文件路径参数)

每个客户端 --> 尝试连接 服务端 --> 如果失败,会启动同目录的 -server.exe , ,继续发送

服务端 对于每个收到的连接,都会转到 一个子线程 去处理数据 --> 这个子线程 又会通知 超时线程

超时线程 会计算超时,时间到 就存入 剪贴板

(注:服务器首次启动 会耗费点时间,因为是低频操作,不然可以 直接开机自启)
(注:一次选中很多文件,需要 修改 MultiSelectModel 为 Player)(个人已经测试 50+文件同时选中,但超过100个 成功不了,具体原因还未知)
(注:端口是 5038 ,若有冲突 可以自行更改)


mermaid-1776002595219.jpg


下面是代码


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),
        }
    }
}



发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

yinlin 发表于 2026-4-13 07:38
快来人救命啊,我头晕,眼花缭乱的
goblack 发表于 2026-4-13 08:30
震惊。本地操作竟然能起多线程上网络服务器。

其实单个进程就行了。

windows vista起就有这个功能了。。。。。之前我在RT的平板上就经常用。因为经常传输文件。win11成为了默认显示出来的选项而已

按住ctrl,或者shift等等多种方式任意方式选择多个文件
选择结束之后按住shift,右键,有一个多出来的选项“复制文件地址”


既然楼主和原文的方法要用到注册表,本身只能在Windows使用,那么我来分享一个只需要注册表就可以实现的方法。并且使用windows shell自带的图标

此方法在WindowsXP年代就在用了。当然XP年代不一定直接可用。。。。

最大的弊病受限于cmd.exe的传参限制,所有的内容长度总和无法超过8192字符
正常Windows路径很少有大于200字符的,但是也很少有c:\windows\explorer.exe那么短
不仅如此,受限于SHELL调用方式的上限,所以实际能选中的文件更少。超过之后,此选项就隐身了,不用担心无法完整复制
还有一个小毛病,在低端电脑、高刷显示器更为明显,能看到小黑窗一闪而过,因为运行了cmd.exe


[Plain Text] 纯文本查看 复制代码
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\copypath]
@="复制完整路径"
"Icon"="shell32.dll,-16762"

[HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\copypath\command]
@="cmd.exe /c echo %1 | clip"


goblack 发表于 2026-4-13 08:43
补充。我给的注册表方法仅供玩耍,实际上对于目前的主流Windows操作系统没有太大的意义,毕竟自带了。

主要是用于当年在Windows7用过这个功能之后,想把这玩意儿带到XP上用,所以写了这么个玩意儿。此方法大概十几年前已经在GitHub开源了。

除了这个作用,另外我写了一些变种,调用powershell,弥补去年之前7-zip不提供右键MD5计算的功能缺失。十几年前就在用。最一开始的时候还不是powershell,是系统里有个证书相关,签名用途的exe能计算sha1 md5的,具体我没印象了,我的记事本也没记录这个"奇技淫巧"
我真正开始好记星不如烂笔头的时候windows 7的安全更新已经步入尾声了,我已经开始使用Windows 10 ltsb 2016了,已经不需要这方法了。但是我感觉powershell的get-filehash方式好像仿佛似乎比正常程序计算sha1 md5慢一些?
onoffon 发表于 2026-4-13 09:03
是不是搞复杂了
Choco 发表于 2026-4-13 09:08
不是想故意泼冷水,但是如果只是复制多行文件路径的话,本地+单线程就可以完成,几乎是瞬间的事,为啥还要多线程+TCP……确实不太理解
 楼主| pyjiujiu 发表于 2026-4-13 09:42
本帖最后由 pyjiujiu 于 2026-4-13 17:13 编辑
goblack 发表于 2026-4-13 08:43
补充。我给的注册表方法仅供玩耍,实际上对于目前的主流Windows操作系统没有太大的意义,毕竟自带了。

...

@onoffon  @goblack  @Choco @yinlin

你们说的都是对的,,欢迎讨论,理不辩不明

这个帖子,最主要的动机是,本人是 rust 新手,刚刚学到 多线程的部分
(Arc + Mutex 这种组合,前者是 Rc 的原子化版本,但无法修改,后者带 inner mutability)
其实 不调用 线程池,用异步也可以,,还有 可以借助管道 ,但还不熟悉


顺便说下 计算 sha256,最近刚好也做过测试
- python 的是最快的(很让我意外),,
- 其次是 原生的 rust 实现(但必须要开 cpu 特定优化)
- 再其次 是 7z 的计算,,
- 最慢的是 windows自带 certutil
(大概数据是 270M的单体文件(新cpu会更快),py 是0.69s,7-zip是1.36,certutil 是1.43 ,)

py快的原因是,因为 Cpython 的 hashlib.py 调用的是 就是 OpenSSL , (因此 直接调用  openssl.exe   是和py差不多)


zhangfeng_223 发表于 2026-4-13 10:43
理不辩不明
heptamemory 发表于 2026-4-13 11:55
用rust的大神,受小弟一拜
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-14 07:51

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表