Windows远程桌面实现之六(新版本框架更新,以及网页HTML5音频采集通讯)

49 篇文章 11 订阅
13 篇文章 5 订阅

                                                                                                   by fanxiushu 2018-08-21 转载或引用请注明原始作者。

到目前为止,包括本文发布了六个系列,能坚持到现在也属不易。
第一篇:
https://blog.csdn.net/fanxiushu/article/details/73269286  windows远程桌面实现之一 (抓屏技术总览 MirrorDriver,DXGI,GDI)
第二篇:
 https://blog.csdn.net/fanxiushu/article/details/76039801 Windows远程桌面实现之二(抓屏技术之MirrorDriver镜像驱动开发)
第三篇:
 https://blog.csdn.net/fanxiushu/article/details/77013158 Windows远程桌面实现之三(电脑内部声音采集,录音采集,摄像头视频采集)
第四篇:
https://blog.csdn.net/fanxiushu/article/details/78869719 Windows远程桌面实现之四(在现代浏览器中通过普通页面访问远程桌面)
第五篇:
 https://blog.csdn.net/fanxiushu/article/details/80996391 Windows远程桌面实现之五(FFMPEG实现桌面屏幕RTSP,RTMP推流及本地保存)

前三篇都是介绍在windows平台采集各种需要的数据,包括各种技术要点。
从驱动到应用层采集,几乎能想到的采集办法都曾尝试过。
第四篇介绍的是如何在现代浏览器中利用最新的HTML5和WebSocket技术在网页上展现和控制远程桌面。
第五篇则是附加功能,实现桌面图像和声音录制到本地视频文件或者推流到RTSP,RTMP服务器等。
这一篇还会介绍一个附加功能:在网页中开启录音,然后打开网页端的远程控制,可以互相说话,类似电话会议一样的功能。
这些文中,并没介绍图像和音频的各种编码和解码算法,
一是因为对这些算法原理不太熟悉,二是这些算法都有成熟的开源库。
这里罗列一下主要使用到的开源库:
ffmpeg, x264,openh264, fdk-aac, libjpeg-turbo, libyuv, openssl, x265 ,libvpx, zlib, liblzma 等等。
(至于更加详细的介绍,可以运行xdisp_virt之后用浏览器打开,查看里边的about页面内容。)
可以看到编码和解码库,用得非常之多。所有的开源库都是静态编译进程序,
因此,你可以把 xdisp_virt.exe单独复制到任何一台windows机器直接运行,而不需要各种依赖库。
这些文章也没提到底层网络通讯部分,这篇文章会有所提及,主要是根据 xdisp_virt软件的内部网络通讯框架来介绍。

总之,实现一个远程桌面,牵涉到的技术挺多,也挺杂。
从windows驱动,各种数据采集,图像音频编解码,网络通讯,
客户端展现和控制,如果是网页客户端还包括javascript以及HTML5等前端技术。

xdisp_virt项目的来源在第一篇(抓屏技术总览 MirrorDriver,DXGI,GDI)和第二篇(抓屏技术之MirrorDriver镜像驱动开发)说得比较明白,
是因为当时开发mirror驱动,需要一个测试程序测试mirror的效果,因此简单开发了一个远程桌面测试程序,
一开始使用jpeg压缩,感觉效果不太理想,后来尝试着使用H264压缩,效果非常好,
同时为了圆大学时候的梦,于是决定继续把这个远程桌面做下去,结果就是一发不可收拾,持续到现在。

后来为了解决在公网也能远程控制内网机器的问题,开发了xdisp_server.exe程序,
让内网的xdisp_virt都链接到运行在公网的xdisp_server程序,然后所有控制客户端连接xdisp_server,这样就能远程控制内网的机器了。
也就是xdisp_server起了一个中转服务器的的作用。

开始前,先提供最新版本程序的下载地址:
CSDN:
https://download.csdn.net/download/fanxiushu/10617305


GITHUB:
https://github.com/fanxiushu/xdisp_virt

基本上包括两个程序。xdisp_virt.exe和xdisp_server.exe(以及两个对应的配置文件),还包含一个mirror驱动,
这个驱动是为了在WINXP,WIN7平台更高效的采集桌面数据而开发的,当然,这个mirror驱动也可以安装到WIN8,WIN10系统中。
均是使用C/C++语言开发。网页客户端包括html网页文件和js脚本文件,都被打包进了程序。

xdisp_server.ini和xdisp_virt.ini配置文件都设置成了默认值,不过有些字段需要你重新设置,
比如 display_name是显示名,
ssl_crt_file和ssl_key_file是SSL证书相关路径,ssl_socket_only如果设置 1,则整个通信只允许SSL加密通讯。
程序只开启一个侦听端口,来接收处理所有的请求,包括加密SSL。Web请求,原生客户端私有协议请求等。
至于程序如何在一个端口中区别这些网络请求。下面会有说明。
web_auth_string字段配置的是网页客户端登录的用户名和密码,
此验证方式很弱,因此强烈要求在SSL环境(也就是HTTPS请求)中使用。
server_ip,server_port,server_auth_string,server_ssl_login字段是xdisp_virt程序登录到xdisp_server服务端相关的配置。
如果server_ssl_login设置为1,表示从xdisp_virt到xdisp_server的整个数据传输通讯都是SSL加密的。
至于 *_allow 字段,则是更加严格的限制只允许哪些客户端的IP地址连接登录,也就是比SSL加密还更安全。

假设运行xdisp_virt.exe程序的机器的IP地址是192.168.100.1, 是内网地址。
假设运行xdisp_server.exe程序的机器的IP地址是 121.1.1.120, 是个公网地址。
把xdisp_virt.ini配置文件的server_ip等字段配置到 121.1.1.120地址。

两个程序以服务方式运行之后(注意取消防火墙的限制)。
你就可以在相同局域网内,在手机等移动设备或者另外的PC电脑打开浏览器,输入 https://192.168.100.1:11000 (假设配置的是11000端口)
或者在任何联网的地方,用手机浏览器或者其他PC电脑浏览器,打开  https://121.1.1.120:32000 (假设xdisp_server端口是32000)

然后就可以非常方便的控制 运行xdisp_virt程序的机器了,以及设置编码参数,设置推流等等。
下图是一些运行效果:
这个是在macOS系统中自带的Safari浏览器,正在远程看电影,远程控制的是 1920X1080的windows 7 的电脑:


这个是在iPhone6手机中的自带浏览器的效果,我的手机比较古老,如果是最新的iPhone手机,对1080p的支持应该会更好。


下图是图像和音频各种编码配置和其他相关配置:


配置的参数比较多,不过应该都能看明白,其中图像编码以H264为主,配置的参数也多。
你如果熟悉x264,可以设置出更适合你的图像效果。

正如第四篇文章(在现代浏览器中通过普通页面访问远程桌面)描述的,在浏览器中是使用javascript解码的,
虽然现在主流浏览器都支持 asm或wasm(
WebAssembly,其实就是把脚本预先编译成中间字节码,类似于java虚拟机制一样的玩意。)
这对这种编码解码的CPU计算密集型应用,wasm是有好处的,虽然号称是接近原生程序的效率。
但实际上测试下来也就能接近50%的效率。
因为我在自己的电脑上,win10, cpu i5 7267u,集显intel 650,使用C/C++开发的原生客户端程序,
远程桌面播放1920X1080全屏视频,CPU占用大概在 10%-15%左右波动,
而且使用的还只是 ffmpeg软解码,图像还只是GDI渲染而非DirectX渲染。
而同样环境下,使用浏览器,CPU占用则在30%-40%, 有时还能看到CPU和GPU的占用一下子上升上去。
可见,在远程桌面的图像激烈活动,比如视频或者打游戏的时候,
使用浏览器作为客户端方式的远程桌面,CPU等资源消耗是挺厉害的,与原生客户端还是没法比。
因此你需要一个配置比较好的电脑,使用浏览器方式控制远程桌面,才能感觉比较爽。
下图是原生客户端的效果图,不过是windows平台的,暂时没精力去做其他平台的客户端。


上图显示的是特殊效果的图像,黑白效果的二值图像,是不是感觉又回到了少年时代的黑白电视的境界?

相比于上次发布的版本,这次更新其实发生了很大变化。
以前是web一个侦听端口,原生客户端一个端口,网络通讯安全程度也比较低。
这次是全部都集成到一个侦听端口,而且增加了SSL安全连接,SSL安全连接也同样集成到同一个端口中。
这么多类型的请求,如何区分呢?
其实也不难,先看WEB请求,当客户端连接成功了,WEB请求不是发送GET,就是发送POST等HTTP命令,
因此第一个字节不是 'G'就是 'P',
而我定义的私有协议,客户端连接成功之后,首先发送的就是登录数据包,第一个字节固定为CMD_LOGIN(定义为1),
再看SSL安全连接,客户端连接成功后,发送的第一个字节固定为 0x16(SSL握手协议类型)。
因此当客户端连接上来,先收取一个字节的数据,根据上面的依据判断,不同的值,分别路由到不同的处理流程。就这么简单,
虽然原理简单,处理起来还是比较复杂。

整个底层通讯框架使用的是windows平台的完成端口模型,
这里使用完成端口不是为了处理连接数多的问题,而是为了处理收发的数据包多的问题。
通讯框架做成异步回调函数方式,简单的说,就是提供一组回调函数,当有客户端连接上来,或者接收到某个客户端的数据,
对应的回调函数都会被调用, 同时提供一个异步发送函数,发送的数据投递给底层框架,直到数据传输完成,调用完成通知回调函数。
如果熟悉高级语言,比如javascript的WebSocket编程方式,就能很清楚的明白这种异步框架方式。
只是这些高级语言中都是集成这种异步框架,程序员只管调用就行,而在C/C++中要实现这种方式,却不是个简单的事情。
这里并不使用第三方框架,而是自己实现,这篇文章也并不打算介绍如何自己实现。

以这种异步通讯框架处理通讯传输,相对图像音频的发送就容易些。
当客户端成功登录上来,加入到一个队列中,在一个线程中定时采集windows桌面图像数据,另一个线程采集音频数据,
采集好之后,做图像或音频编码,编码完成之后,组成一定格式的数据包,这个数据包就是发送给客户端的图像和音频数据。
然后遍历客户端的队列,对每个客户端,调用通讯框架的异步发送函数,就这样数据包就被发送给每个客户端了。

这就是xdisp_virt和xdisp_server底层通讯的基本框架,
当然具体处理的数据包远不止图像音频数据包,还包括其他许多类型的数据包,处理的逻辑也比较多和杂。
xdisp_server还牵涉到一个采集端和多个客户端如何关联等一系列问题。

当初把web和原生侦听端口合并到一起,是有一次发神经,把原生客户端连接到web端口,死活都连不上,还以为程序出问题了。
折腾了半天,原来是端口搞错了,因此决定合并到一起。
再到后来,发现在页面上可以录音,
然后把录音传给被控制端,被控制端播放,然后播放的声音又被采集到,再回传回来或者回传给其他客户端。
就这样,组成一个电话会议室。
因此就想在web中加入录音功能,这需要调用webrtc标准中的GetUserMedia函数,然后研究发现有些浏览器,
比如chrome,Safari等必须是在https安全环境下,才能正常调用GetUserMedia获得话筒。
于是,决定重新打造整个程序通讯,让它 支持SSL安全连接,其实这个很早就打算实现的,因为在WEB环境中,
全是明文传输,虽然音视频数据通讯的是WebSocket中定义的私有协议,但是还是很容易被第三方破解和侦听到。
只是一直没有说服自己的修改支持SSL安全连接的动力。

SSL是在TCP之上的一个加密层,我并不想做颠覆性的修改原来程序的整个通讯框架,
而只是想在原来的异步通讯框架函数层上同样也增加一层SSL加密和解密框架函数层,
这样原来上层调用的异步框架函数,全部改成调用SSL的框架函数,而SSL框架则调用下层的异步通讯函数。

要这样做,首先要考虑的就是需要把SSL加密和解码过程,跟socket网络通讯分隔开来,不能凑在一起。
这里使用openssl开源库,仔细研究openssl开源库,还真可以把socket和SSL加密解密分隔开来。
openssl的加密解密都是基于BIO操作的,BIO是openssl定义的一个包容器对象,
BIO可以定向到socket类型,也可以定向到内存类型,或者其他类型。
显然我们加密解密都是把BIO定向到内存中。

初始化的伪代码看起来如下:
    SSL* ssl=SSL_new(ctx);
    BIO*  read_bio = BIO_new(BIO_s_mem()); /创建内存类型的BIO ,读
    BIO*  write_bio= BIO_new(BIO_s_mem());/写
    ....
    SSL_set_bio(ssl, read_bio, write_bio);  ///读和写的BIO关联到SSL中。
    .....
   
然后我们如果从网络通讯底层接收到对方发来的已经加密的SSL数据 enc_data,长度为 enc_len 。
我们需要把enc_data写入openssl的read_bio中去解密,这时候调用BIO_write函数,如下
      BIO_write(read_bio, enc_data,enc_len);
调用BIO_write函数之后,再调用SSL_read函数读取内存BIO解密的数据,如下:
     SSL_read(ssl, dec_buf, dec_len); // dec_buf接收解密之后的数据,这个就是我们需要的数据。

反过来,当我们有数据需要发送,调用SSL_write把数据写到write_bio中去加密,如下:
    SSL_write(ssl,  buffer, length);// buffer就是要写的数据
数据被写到openssl的BIO内存之后,需要检测write_bio的加密的数据是否完成需要读取出来。
这时候调用 BIO_ctrl_pending 函数检测是否有数据,
如果返回大于0,表示有数据,需要调用BIO_read读取已经加密了的数据,如下:
    enc_len= BIO_read(write_bio, enc_buffer, length);///返回值就是读取到的数据长度,
然后我们再把读取到的已经加密的enc_buffer数据通过底层网络发送出去。

同时注意,在建立SSL连接阶段,调用 SSL_do_handshake 函数进行交互。
SSL_do_handshake和SSL_read调用之后,都极有可能产生数据需要发送给对端进行交互的数据,因此,每次调用完这两个函数,
必须再次调用 BIO_ctrl_pending 检测是否需要数据发送,是的话,必须及时发送出去,才能完成接下来的SSL数据交换。

代码片段如下,代码摘自xdisp_virt代码工程:

int __ssl_layer_t::read(char* buf, int length)
{
    int ret = 0;
    bool retry = false;

L:
    if (length > 0) {
        ERR_clear_error();
        ret = BIO_write(this->r_bio, buf, length); //写到ssl内存进行加密处理
        if (ret == length) { // success

        }
        else {
            ///
            if (BIO_should_retry(this->r_bio)) {
                ret = 0;///
                retry = true;
                printf("BIO_write: BIO_should_retry \n");
            }
            else { // error

                printf("BIO_write -> input bio error.\n");
               
                return -1;
            }
            //
        }
        //
    }

    while (true) {
       
        if ( !this->is_connected ) { //如果在握手阶段,
            /
            ERR_clear_error();
            ret = SSL_do_handshake(ssl);
            if (ret == 1) { // success
               
                this->is_connected = true;

                /// callback

                ret = this->ptr_new_client(&layer, this->param); ///
                if (ret < 0) {
                    return -1;
                }
                //
                goto L2; ///开始读数据

            }
            else {
               
                if (is_fatal_error(ssl, ret)) {
                    ///
                    printf("SSL_do_handshake ret=%d, err=%d\n", ret, SSL_get_error(ssl,ret) );
                    return -1;
                }
                //
            }
            //
            break;
        }

       
    L2:
        ERR_clear_error();
        ret = SSL_read(this->ssl, this->recv_buffer + this->recv_pos, this->recv_length - this->recv_pos); //从ssl内存解码
    //    printf("");
        if (ret <= 0) { // <=0 出错或者数据不够
            if (is_fatal_error(ssl, ret)) {
                ///
                if (SSL_get_error(ssl, ret) == SSL_ERROR_SYSCALL) {//非常诡异的问题,!!!
                    printf("***-- SSL_read: warning SSL_ERROR_SYSCALL, continue process---- \n");
                    break;
                }
               
                printf("SSL_read: error. ret=%d, err=%d\n", ret, SSL_get_error(ssl, ret) );
                return -1;
            }

            break;
           
        }

        ///

        this->recv_pos += ret;
        /
        if (this->is_any_length) { //接收到任何长度都调用回调函数

            this->recv_buffer[this->recv_pos] = 0; ///
           
            int curr = this->recv_pos;

            ///callback
            int r = this->ptr_read(&layer, recv_buffer, this->recv_pos, this->param);
            if (r < 0)return -1;

            ///
            if (this->recv_pos >= this->recv_length) this->recv_pos = 0; ///重新开始

            if (this->is_keep_recv_pos) {
                this->is_keep_recv_pos = false;
                if (curr < this->recv_length)this->recv_pos = curr;
            }
           
        }
        else {
            //接收到指定长度
            if (this->recv_pos == this->recv_length) {
               
                int curr = this->recv_pos;

                ///callback
                int r = this->ptr_read(&layer, recv_buffer, this->recv_pos, this->param);
               
                /
                this->recv_pos = 0; ///
                if (this->is_keep_recv_pos) { ///
                    this->is_keep_recv_pos = false;
                    if (curr < this->recv_length)this->recv_pos = curr;
                }
                /
                if (r < 0)return -1;
               
            }
            ///
        }
        /
    }

   
    w_lock();
    ret = _check_write();
    w_unlock();
    if (ret < 0) {
        return -1;
    }

    if (retry) {
        retry = false;
        goto L;
    }

    //

    return length;
}

int __ssl_layer_t::_check_write()
{
    int pending = BIO_ctrl_pending(this->w_bio);//BIO内存是否有数据发送
    if (pending > 0) {
        /// write

        if (send_size < pending || !send_buffer) {
            if (send_buffer)free(send_buffer);
            send_size = pending + 512;
            send_buffer = (char*)malloc(send_size);
        }

        ///
        ERR_clear_error();
        int ret = BIO_read(this->w_bio, this->send_buffer, this->send_size);
        if (ret <= 0) {
            if (is_fatal_error(ssl, ret)) {
                printf("*** BIO_read ret=%d, err=%d\n", ret, SSL_get_error(ssl, ret));
                return -1;
            }
        }
        else {
            /// callback
            ret = this->ptr_write(&layer, this->send_buffer, ret, this->param);
            if (ret < 0) {
                printf("*** SSL ptr_write to socket err\n");
                return -1;
            }

            /
        }

    }

    return 0;
}
以上代码中ptr_new_client,ptr_read,ptr_write都是回调函数,
ptr_new_client表示SSL握手完成,有SSL客户端连接上来了。
ptr_read,是读取到SSL并且解码了的数据,
ptr_write是加密了的数据,需要通过网络发送出去。

至此,网络通讯部分简单介绍到此,主要介绍一些重点内容,其他方面的这里也就不一一列举了。

在上面的一些效果图中,会看到一个红色按钮 “开启话筒”, 这个功能就是上面简单提到过的,
在浏览器中录音,录音数据经过编码发给被控制端,被控制接收并且播放,这样被控制端和控制端就可以互相通话。
然后被控制端采集电脑内部的声音,也把这个录音也采集到了,结果发给所有连接上来的客户端,这样其实就是组成了一个简单电话会议。

新版本xdisp_virt的程序,不单可以采集电脑内部声音,还可以采集电脑话筒一共4路音频数据,
然后这些音频数据经过混音处理,发给控制端播放,当做完这个功能时候。
就想着反过来,把控制端的声音也录制下来,发给被控制端。于是就有了在浏览器“开启话筒”的功能。
虽然好像没啥大用处,也就是好玩。这个功能,也就是玩玩的效果,如果使用中有什么好的建议和意见,不妨提出来。

这里说说在浏览器中如何采集录音数据,以及通过javascript压缩成mp3,然后再传输给被控制端。
浏览器中采集录音和摄像头的数据,需要调用GetUserMedia 接口,这个是属于WebRTC标准中的一部分,应该属于数据采集那部分吧。
再翻看各种浏览器为了实现这些功能的历史,真是各个浏览器混战的年代,直到最近才标准化为WebRTC。
所以各个浏览器接口稍有不同,如下方式调用:

       window.AudioContext = window.AudioContext || window.webkitAudioContext;
        navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
                                     navigator.mozGetUserMedia || navigator.msGetUserMedia;
        window.URL = window.URL || window.webkitURL;

      navigator.getUserMedia({ audio: true }, // 这里只采集话筒
            function (stream) {
                ///
                var audio_context = new window.AudioContext;
                record_source = audio_context.createMediaStreamSource(stream);
                record_node = (audio_context.createScriptProcessor ||
                        audio_context.createJavaScriptNode).call(audio_context, 1024*4, 2, 2);// 设置缓存长度,双声道

               record_node.onaudioprocess = function (e) {
                    var left = e.inputBuffer.getChannelData(0); // left
                    var right = e.inputBuffer.getChannelData(1);// right
                    left和right就是采集到的录音数据,以是float类型,范围从 -1到1,
                    / 接着我们就可以把这数据通过javascript编码成需要的类型,这里编码成mp3,使用的是GITHUB上的开源库:
                    https://github.com/zhuker/lamejs
               }
              .......
              /连接起来,开启录音
              record_source.connect(record_node);
              record_node.connect(audio_context.destination);
          },
          function (e) {
              alert('err: '+e);
         });

看起来够简单吧,有兴趣可以下载我在CSDN和GITHUB提供的HTML和JS相关代码,
虽然xdisp_virt和xidsp_server公布的只是程序,但是网页客户端的全部html和js文件都提供了。
js解码全是从GITHUB找的开源库,主要包括h264bsd和jsmpeg两个,以及其他一些音频js解码库。
如果没有GITHUB共享的这些图像音频的 js 解码代码,是无法实现网页客户端的。

  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
### 回答1: 可以使用Python的pywin32模块来调用Windows API,从而实现Windows远程桌面控件的控制。例如,可以使用pywin32的“ win32gui.SendMessage ”函数来发送消息给远程桌面控件,从而控制它们的行为。 ### 回答2: 通过pywin32调用Windows API可以实现Windows远程桌面控件的控制。下面是一个简单的例子: 首先,需要安装pywin32模块。可以使用pip命令安装:pip install pywin32。 然后,在Python中导入所需的模块: ```python import win32com.client import win32gui import win32process ``` 接下来,通过Windows API获取远程桌面窗口的句柄和进程ID: ```python def get_remote_desktop_window(): # 通过窗口标题查找远程桌面窗口 hwnd = win32gui.FindWindow(None, "Windows 远程桌面") if hwnd != 0: _, process_id = win32process.GetWindowThreadProcessId(hwnd) return hwnd, process_id else: return None, None ``` 然后,使用Windows API进行远程桌面控制: ```python def control_remote_desktop(): hwnd, process_id = get_remote_desktop_window() if hwnd is not None: # 使用Shell.Application创建远程桌面对象 shell = win32com.client.Dispatch("Shell.Application") # 获取远程桌面窗口的控件对象 desktop = shell.Windows().Item(process_id) # 使用控件对象进行相关操作 desktop.FullScreen = True # 进入全屏模式 desktop.ControlType("SendKeys").Application.SendKeys("Hello, World!") # 发送按键消息 ``` 在上述例子中,我们首先通过查找窗口标题为"Windows 远程桌面"的窗口来获取远程桌面窗口的句柄和进程ID。然后使用Shell.Application创建远程桌面对象,并通过控件对象进行相关操作,如进入全屏模式、发送按键消息等。 需要注意的是,以上示例只是对通过pywin32调用Windows API实现远程桌面控件的控制的一个简单演示,并不包含详尽的远程桌面控制功能。实际应用中,可能需要更多的Windows API调用和错误处理来完善远程桌面控制的功能。 ### 回答3: 通过pywin32库可以调用Windows API来实现Windows远程桌面控件的控制。远程桌面控件是Windows操作系统中的一个重要组件,允许用户在本地计算机上访问和控制远程服务器或计算机。 下面是一个使用pywin32实现Windows远程桌面控件的控制的例子: ```python import win32com.client # 创建远程桌面控件对象 rdp = win32com.client.Dispatch("MsTscAx.MsTscAx") # 设置连接参数 rdp.Server = "远程服务器IP地址" rdp.UserName = "用户名" rdp.Domain = "域名" rdp.AdvancedSettings2.ClearTextPassword = "密码" # 连接远程服务器 rdp.Connect() # 等待连接完成 while rdp.Connected != 1: pass # 控制远程桌面 rdp.fullScreen = True # 全屏显示远程桌面 # 将焦点切换到远程桌面 rdp.FullScreenTitle = "远程桌面" rdp.FullScreenMenuBar = False rdp.SecuredSettings2.KeyboardHookMode = 1 rdp.SecuredSettings2.AudioRedirectMode = 0 # 断开连接 rdp.Disconnect() ``` 上述代码首先创建了一个远程桌面控件对象,并通过设置连接参数进行服务器的连接。在连接成功后,可以通过控制远程桌面的相关属性,例如设置全屏显示、切换焦点等。最后,断开与远程服务器的连接。 需要注意的是,使用pywin32调用Windows API时,需要确保已经安装了相应的pywin32库,并且在程序中使用了相应的COM组件的ProgID。 这个例子只是简单展示了pywin32调用Windows API实现远程桌面控件的控制,实际应用中还可以根据具体需求进行更复杂的操作,例如文件传输、远程命令执行等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值