Windows远程桌面实现之十七:基于浏览器的文件和目录传输(二)
by fanxiushu 2025-05-08 转载或引用请注明原始作者。
接上文,
上文主要阐述在浏览器中实现文件和目录传输,都是基于专门的web页面实现的。
本文则需要把文件和目录传输功能,合并到xdisp_virt软件的 Web Terminal 和 远程桌面功能中。
我们先看看如何合并到 Web Terminal 。
Web Terminal就是xdisp_virt实现在浏览器上的一个类似 ssh 这样的功能,
ssh这类终端,都有类似 rzsz 这样的命令用来上传下载文件或目录。
当然,这里不打算实现rzsz,也不会使用他们的通讯协议,而是根据自己软件的功能,
结合上文介绍的在浏览器网页中传输文件目录的这种特点,使用自己开发的命令。
于是,首先我们得创建上传和下载两个命令。
至于这个问题,当时折腾了挺长时间,
因为并不想再做两个专门的程序来上传下载文件,
于是首先把主意打到xterm.js和伪终端的通讯协议上,打算通过拦截协议内容,截获到xterm.js输入的命令,
然后转交到我们手上处理,结果以失败告终,理论上应该可以实现,但是只是理论上。
实际操作非常麻烦,要非常熟悉终端协议和各种转义符,解析起来也烦,
动不动就是各种毛病,在一个操作系统成功了,到另外一个不同系统又出问题,所以只能失败告终。
再到后来,干脆给 xdisp_virt程序增加一个-terminal命令参数模拟上传下载这两个命令,看起来像下面这样:
xdisp_virt -terminal down 路径等参数
xdisp_virt -terminal up 路径等参数
但是如果只是这样,用户在输入命令的时候肯定会很不爽,因为输入的参数太多,
于是在linux和macOS系统使用 alias 命令,windows使用doskey命令 简化成 down和up两个命令,类似如下:
alias down='xdisp_virt -terminal down'; alias up='xdisp_virt -terminal up' |
这样总算把命令的名字问题搞定了。
当然简化成down和up这两个命令运行的时候,肯定会是另外的进程,因此这里也会牵涉到与xdisp_virt主进程通讯问题,
而且还必须准确定位到所属于哪个terminal终端,因为终端可以随意创建多个。
这里采用通过父进程ID的方法来识别,来达到这种关联关系。
现在大致说说down命令的工作流程。
当在web terminal终端也就是浏览器 ,输入 down "Path" 或其他命令参数的时候,
此命令传递到服务端,服务端terminal通过alias设置确定执行的是 A: xdisp_virt -terminal down "Path"
于是带上相关参数执行此命令 A,A在内部与主程序通讯,并确定Path路径有效性,以及是否是目录等。
最终生成有效的URL连接等参数,把参数发给主程序,主程序定位到是哪个terminal的请求,
然后把这些参数通过与这个terminal连接的WebSocket发给对应的浏览器端,
浏览器端的js脚本获取到这些参数,于是生成 下载连接,
于是,浏览器就会弹出对应的下载连接。
一旦浏览器朝xdisp_virt主程序发起此URL请求,
如果是文件,则按照传统方式提供文件下载。
如果是目录,xdisp_virt就开始进行流式zip打包,
并同时传递数据给浏览器,zip流式打包可查阅上一篇的文章。
up命令总体类似,不过上传目录和文件则是按照上一章的方式,通过WebSocket来上传文件和目录。
同时Web Terminal 也实现了拖动上传,也就是说,
如果把文件或者目录直接拖到到 浏览器上的web terminal窗口中,
然后就会自动的上传文件和目录,上传的位置就是当前 terminal在 服务端的工作目录。
简单阐述完web terminal中集成文件目录上传下载的功能之后,
我们再来看看xdisp_virt的远程桌面功能中如何集成文件和目录的上传下载。
首先我们看看在普通的远程桌面客户端的实现中,
如何做到集成到远程桌面中的文件目录上传下载,
其实挺简单:“复制粘贴” 就可以了。
对于普通的远程桌面客户端,在本地的文件资源管理器或类似的地方,选择目录或文件,然后点“复制”,
接着进入远程控制,然后同样在远程电脑中的资源管理器或类似的地方,选择“粘贴”,
这样一个文件或目录就顺利的上传到远端电脑中了,同理从远端下载文件也一样。
这就跟在本地电脑不同目录之间“复制粘贴”一样的方便。
但是我们面临的是浏览器客户端,所以”复制粘贴“实现起来可能就有点奇葩了。
主要原因就是浏览器本身对读写本地文件的严格限制。
我把在浏览器实现的这种”复制粘贴“行为称之为: “非对称的复制粘贴”
为何这么说呢,看看下面的视频演示:
(演示视频主要演示web terminal 集成的文件上传下载效果,
以及远程桌面中这种“非对称的复制粘贴” 效果)
xdisp_virt软件文件目录上传下载演示2
从视频演示中,我们能看到,
当远端复制一个文件或目录的时候,在浏览器客户端会弹出一个远端复制了文件或目录的提示,
让我们选择主动去下载。
同样的,我们拖动一个文件或目录到浏览器页面上,或者在页面上选择上传,
这个时候按道理说是直接上传到远端就行了,可关键是我们并不知道要上传到远端的哪个目录中,
因此只好在远端对应目录中选择“粘贴”操作。
所以可以看出来,在浏览器客户端,“复制粘贴” 被拆成了能被浏览器接受的,不被限制的操作模式,
所以我才把这种行为模式称为:“非对称的复制粘贴”。
这是在折腾了许久,综合了普通远程桌面的“复制粘贴”的便利性,以及浏览器本身的限制情况,
才弄出来的相互接近又相互妥协的方案,反正是没找到更好的办法了。
我们再来看看比较轻松一些的,在浏览器端如何实现文件拖动上传。
为何说轻松呢?我们以web Terminal窗口拖动上传为例,
假设我们提供给 xterm.js 的div元素的id 是“term”,
我们要拖动文件或目录到term窗口,只需响应如下事件即可:
document.getElementById('term').addEventListener("dragover", function (event) {
event.stopPropagation();
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
});
document.getElementById('term').addEventListener("drop", function (event) {
event.preventDefault();
const items = event.dataTransfer.items;
if (items.length == 0) return;
接着我们需要处理这个items
///
});
我们需要在drop的事件函数中 解析拖放的items,
解析过程牵涉到调用js的webkitGetAsEntry, ReadEntries等函数,同时需要递归调用,
很有点类似我们在普通程序中递归遍历一个目录的情况。
这里也就不再赘述,反正为了处理上的统一,
我把它最终解析成了一组file对象,与file类型的input元素的上传生成的对象一致。
浏览器拖动上传文件或者input上传生成files文件列表已经有了,也就是浏览器端已经准备好文件了。
现在的问题是如何把它传递给xdisp_virt远程桌面功能的远端电脑。
不像Web Terminal, 当我们把文件拖动到Web Terminal的网页上的时候,
我们可以通过一些技术手段,比较容易就能获取到Terminal当前的工作目录,
于是拖动文件到web terminal的网页的时候,直接上传到terminal的当前目录,这无可厚非。
我们的xdisp_virt远程桌面功能,其实就是在浏览器中远程到远端电脑的屏幕桌面,
这种情况下,是无法获取到当前工作目录的。
当然有些想法,比如当前远程桌面刚好打开了资源管理器,然后拖文件到浏览器的远程桌面窗口中,
然后刚好拖动到资源管理器区域,这样不正好拖动到资源管理器中了吗?
想法是挺简单的,但是实现起来却是非常糟糕的,甚至是很难实现的。
要做到这个想法,我们可以利用超前一点的知识,做图像识别,
是的,图像识别。
通过图像识别算法,识别出我们远程桌面屏幕中的资源管理器,
同时识别出鼠标移动到当前的资源管理器区域的目录路径,
这个说起来简单,但是你得考虑不同的操作系统的资源管理器界面的不同,
同时同一种操作系统不同版本的资源管理器差别也极大。
就为了获得一个目录路径,就放这么一个大招,实在不划算。
所以我们可以看到目前的的远程桌面工具,基本都没有拖动上传这一个选项,都是“复制粘贴”。
而浏览器使用拖动上传,实在是没办法,
因为浏览器中没有在本地复制文件,然后浏览器能获得复制的文件的这种功能。
因而退而求其次:在浏览器拖动,在远端桌面选择”粘贴“,相当于在浏览器端把”复制“改成了拖动。
最后我们看看远程桌面中的某个资源管理器的目录中选择“粘贴”的时候,如何做到把我们浏览器上的文件上传的。
这里以windows为例,
(其他系统macOS,linux目前没做研究,目前也没那个精力,
再说xdisp_virt提供的文件上传是多种途径的,
远程桌面用不了,可以用web terminal上传,或者干脆专门的上传页面上传)
在windows,我们使用OLE剪贴板,通过IDataObject对象来传递文件。
具体做法就是我们实现 一个继承IDataObject 接口的类,同时还需要实现继承IStream接口的类,
IDataObject负责管理整个传输,比如多个文件的上传。
IStream接口负责处理单个文件,比如这个接口里边的Read接口函数,就负责读取文件内容。
至于具体的实现过程可以查看我发布到github上的源码:
https://github.com/fanxiushu/ole_clipboard-send_virtual_file
源码是从xdisp_virt软件中提取出来的,提供的接口也挺简单,可以直接用的自己的项目中,
相信大部分人都能一看就会使用。
支持Web Terminal 和文件目录传输的新版本xdisp_virt已经发布到github上,
https://github.com/fanxiushu/xdisp_virt
有兴趣可下载使用。