当前位置: 首页 > wzjs >正文

新闻网站建设现状分析物联网开发

新闻网站建设现状分析,物联网开发,常宁市住房和城乡建设局网站,做外贸搜索外国客户的网站前言 本人的国产化项目涉及在国产Linux系统(宿主机)与window系统(虚拟机)的应用窗口交互功能:国产Linux系统与window生成一对一匹配的虚拟应用窗口,当点击虚拟应用窗口时,window端需要从任务栏中…

前言

        本人的国产化项目涉及在国产Linux系统(宿主机)与window系统(虚拟机)的应用窗口交互功能:国产Linux系统与window生成一对一匹配的虚拟应用窗口,当点击虚拟应用窗口时,window端需要从任务栏中激活并显示到桌面最顶端。本篇将分别讲解国产桌面系统(统信UOS和麒麟kylin系统)和window10上通过窗口句柄实现对窗口的操作。

国产Linux桌面系统(宿主机)

1、创建窗口应用

QT中创建一个QWidget项目,在初始化函数中,启动窗口的任务栏点击事件、设置按钮和尺寸等属性。

    ui->setupUi(this);// 启用窗口的任务栏点击事件setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint);// 设置窗口标志,移除最大化、最小化和关闭按钮this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint);setWindowOpacity(0);  // 设置窗口为完全透明resize(1, 1);  // 设置窗口为一个最小的尺寸

2、发送(打开指定应用程序命令)信息到虚拟机端

获取到当前程序的进程ID,以及要打开的window应用绝对路径,一起发送给虚拟机window端的QT编写的win服务器进程。

#include "networkclient.h"
void sendpack(const QString& Ip,netPackQ packet){// 创建一个客户端连接NetworkClient client(Ip, PORT);// 发送数据client.sendData(packet);
}int main(int argc, char *argv[])
{QApplication a(argc, argv);pid_t pid = QCoreApplication::applicationPid();.....
}

发送端的NetworkClient类如下:

//"networkclient.h"头文件
#ifndef NETWORKCLIENT_H
#define NETWORKCLIENT_H#include <QObject>
#include <QTcpSocket>
#include <QDataStream>
#include <QByteArray>
#include <QString>
#include <iostream>
#include <QTimer>//请求包结构
typedef struct  netPackQ
{char opCode;char keyword[254];netPackQ(){memset(opCode,0,1);memset(keyword,0,254);}
} netPackQ;class NetworkClient : public QObject
{Q_OBJECT
public:explicit NetworkClient(QObject *parent = nullptr);NetworkClient(const QString &host, int port);~NetworkClient();void sendData(const netPackQ &data) ;bool checkPortOpen(const QString &host, quint16 port, int timeout = 1);bool m_blink_status;
private:QTcpSocket *socket;
};#endif // NETWORKCLIENT_H
//networkclient.cpp文件
#include "networkclient.h"NetworkClient::NetworkClient(const QString &host, int port)
{m_blink_status=false;socket = new QTcpSocket(this);// 连接到服务器socket->connectToHost(host, port);if (!socket->waitForConnected(2000)) {std::cerr << "连接服务器失败!" << std::endl;return;}else m_blink_status=true;std::cout << "已连接到服务器!" << std::endl;
}NetworkClient::~NetworkClient() {if (socket->isOpen()) {socket->close();}
}void NetworkClient::sendData(const netPackQ &data) {if (socket->state() == QAbstractSocket::ConnectedState) {// 创建数据流QByteArray byteArray;QDataStream out(&byteArray, QIODevice::WriteOnly);out.setVersion(QDataStream::Qt_5_12);  // 设置版本,避免Qt版本变化导致问题// 写入数据结构out.writeRawData(data.opCode, sizeof(data.opCode));out.writeRawData(data.keyword, sizeof(data.keyword));// 发送数据socket->write(byteArray);socket->flush(); // 确保数据立即发送std::cout << "数据已发送!" << std::endl;}
}qint64 getcurtime(){// 获取当前时间戳(秒级)std::time_t timestamp = std::time(nullptr);return timestamp;
}bool NetworkClient::checkPortOpen(const QString &host, quint16 port,  int timeout) {qint64 bgtime=getcurtime();QTcpSocket socket;QTimer timer;// 设置超时机制timer.setSingleShot(true);// 设置连接超时为1秒QObject::connect(&timer, &QTimer::timeout, [&]() {qDebug() << "Connection attempt to" << host << "on port" << port << "timed out!";socket.abort();  // 取消当前连接尝试});// 尝试连接目标主机和端口socket.connectToHost(host, port);// 启动计时器来控制超时timer.start(timeout);// 等待连接状态变化if (socket.waitForConnected(timeout)) {// 连接成功qDebug() << "Successfully connected to" << host << "on port" << port;socket.disconnectFromHost();return true;  // 表示端口可用} else {// 连接失败或超时return false;  // 表示端口不可用}qint64 endtime=getcurtime();std::cout << " checksqlonline use time:" << endtime-bgtime << std::endl;return false;
}

需要在.pro文件中加入network

QT       += network

3、点击宿主机任务栏上的窗口应用图标时,发送(指定应用激活并置顶到桌面)信息到虚拟机端

需要重写事件函数event(),增加发送事件函数sendevent();

    // 重写事件处理函数bool event(QEvent *event) override{// 判断是否是窗口激活事件if (event->type() == QEvent::WindowActivate) {//发送窗口激活命令到window系统端sendevent();return true;  // 事件已处理}// 调用基类的事件处理return QWidget::event(event);
}void sendevent(){// 创建一个客户端连接NetworkClient client(m_Ip, PORT); //if (client.m_blink_status==false){std::cout << client.m_blink_status << std::endl;QMessageBox::information(this, "提示框", "无法链接对端进程管理器,程序将退出");this->close();}else {// 创建一个数据包实例并填充数据netPackQ packet;packet.opCode=1;//发送应用发布窗口信息sprintf(packet.keyword, "%d",m_Pid);  // 设置关键字// 发送数据client.sendData(packet);}
}

深入的优化需求场景一

当虚拟机客户端缩小到宿主机(国产系统)的任务栏时,用户进行以上的激活操作是无法让虚拟机窗口显示在宿主机桌面的,以下介绍三种“国产系统(统信UOS、麒麟kylin桌面系统)让缩小到任务栏的第三方应用窗口,激活并显示到桌面”的解决方案(只有最后一个有效)。

方案一:QT的Xlib库
#include <QtCore/qtextstream.h> 
#include <QApplication>
#include <QtWidgets/QWidget>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#include <iostream>
#include <QDebug>void activateWindow(Window windowId) {std::cout << __LINE__ << std::endl;// 打开 X11 显示连接Display *display = XOpenDisplay(nullptr);if (!display) {qDebug() << "Cannot open X11 display!";std::cout << __LINE__ << std::endl;return;}// 获取窗口信息XWindowAttributes winAttributes;if (!XGetWindowAttributes(display, windowId, &winAttributes)) {qDebug() << "Cannot get window attributes!";std::cout << __LINE__ << std::endl;XCloseDisplay(display);return;}// 将窗口显示到桌面(确保它不被最小化)XMapWindow(display, windowId);  // 将窗口显示// 将窗口置顶XRaiseWindow(display, windowId);// 激活窗口(通过设置焦点)XSetInputFocus(display, windowId, RevertToParent, CurrentTime);// 确保修改生效XFlush(display);XCloseDisplay(display);std::cout << __LINE__ << std::endl;
}int main(int argc, char *argv[])
{if (argc < 2) {//外部传入windowId方式std::cout << "Usage: " << argv[0] << " <windowId>" << std::endl;return -1;}// 从命令行参数获取窗口 ID,并将其从字符串转换为 Window 类型QByteArray windowIdStr = argv[1];  // 获取参数,假设它是一个十六进制字符串Window windowId = strtoul(windowIdStr.constData(), nullptr, 16);  // 将字符串转换为 Window ID(十六进制)//Window windowId = 0x07a00006;//可以用这条进行测试// 激活该窗口并将其置顶activateWindow(windowId);return 0;
}

需要在.pro文件中加入库

LIBS += -lX11

以上程序需要的传入的参数windowId是通过命令行wmctrl -x -l获取的第一列信息(后文专项讲解)。经过测试,以上功能可以让缩小到任务栏的窗口应用有响应(变黄色并且闪烁),但未显示到桌面,不符合项目需求(后面有符合需求的解决方案)。

方案二:QT的<Qwindow>
#include <QApplication>
#include<QWindow>
void showwindow(Window windowId){QWindow *window = QWindow::fromWinId(windowId);if(window){window->show();window->requestActivate();//QGuiApplication::focusWindow();window->raise();std::cout << __LINE__ << std::endl;}else{std::cout << __LINE__ << std::endl;}
}int main(int argc, char *argv[])
{QApplication a(argc, argv);if (argc < 2) {std::cout << "Usage: " << argv[0] << " <windowId>" << std::endl;return -1;}// 从命令行参数获取窗口 ID,并将其从字符串转换为 Window 类型QByteArray windowIdStr = argv[1];  // 获取参数,假设它是一个十六进制字符串Window windowId = strtoul(windowIdStr.constData(), nullptr, 16);  // 将字符串转换为 Window ID(十六进制)// 假设我们从 wmctrl 获取到的窗口句柄是 0x06400002//Window windowId = 0x07a00006;// 激活该窗口并将其置顶showwindow(windowId);return a.exec();
}

以上程序需要的传入的参数windowId是通过命令行wmctrl -x -l获取的第一列信息(后文专项讲解)。经过测试,以上功能可以让缩小到任务栏的窗口应用有响应(变黄色并且闪烁),但未显示到桌面,不符合项目需求(后面有符合需求的解决方案)。

方案三:系统工具wmctl

第一列(例如:0x0c000006,标识为CLASSID)是当前的窗口句柄,第三列(peony-qt-desktop.桌面,标识为CLASS)是窗口类,第四列(xxxx 桌面,标识为win_name)是应用窗口的名称。

系统级工具wmctrl,通过命令行:

wmctrl -x -r <CLASS> -b remove,hidden && wmctrl  -x -a <CLASS>

可以让激活任务栏中的指定类型的窗口激活并显示到桌面,当时有一个缺点,一个类型有多个窗口打开时,只能激活其中一个(窗口ID最小的),比如多个文件管理器缩小到任务栏,此命令只能激活一个显示到桌面,如果某个应用是唯一的,可以使用此命令完成交互效果(比如我需要激活vbox客户端,在我的项目中它是唯一显示到桌面的)。

如果需要根据窗口句柄进行激活,可以达到精准的激活,以下是命令行:

wmctrl -i -r <CLASSID> -b remove,hidden && wmctrl -i -a <CLASSID>

深入的应用场景二

        如果用户右键点击宿主机(国产系统)任务栏上虚拟应用窗口的右键“退出”功能时,通常系统会直接把此窗口杀死,此时同步发送关闭应用命令给到虚拟机进行关闭,这样能达到初级的事件同步闭环。

        深入的场景时,如果发送关闭事件给到虚拟机(window系统),虚拟机的窗口关不掉呢,比如正在打开的记事本有编辑的内容未保存时,会弹窗提示是否关闭,如果选择“不关闭”,此时虚拟机中的应用窗口就会“遗留”。解决这个问题的方法,就需要在宿主机(工程系统)重载窗口的关闭事件closeEvent()来先阻止窗口关闭,等待虚拟机(window)正在的关闭窗口之后,再同步发送真是关闭窗口的信息回来之后,再关闭/杀掉虚拟机窗口。

// 重写 closeEvent 事件---接收在任务栏上右键点击“关闭所有”的菜单功能事件---20250218void closeEvent(QCloseEvent *event) override {std::cout << "====截获关闭窗口命令,等待window返回关闭指令====" << std::endl;event->ignore();  // 拒绝关闭窗口sendkill();
}voidsendkill(){// 创建一个客户端连接NetworkClient client(m_Ip, PORT);if (client.m_blink_status==false){std::cout << client.m_blink_status << std::endl;QMessageBox::information(this, "提示框", "无法链接对端进程管理器,程序将退出");this->close();}else {// 创建一个数据包实例并填充数据netPackQ packet;packet.opCode = 2;  // 关闭窗口sprintf(packet.keyword, "%d",m_Pid);  // 设置关键字// 发送数据client.sendData(packet);}
}

另外,“等待虚拟机(window)正在的关闭窗口之后,再同步发送真是关闭窗口的信息回来之后,再关闭/杀掉虚拟机窗口”这个功能,我另外写了一个TCP服务,接收虚拟机端主动的关闭的窗口进程,然后同步杀死宿主机端对应的进程ID(这一块没有技术难点,这里就不展示了,参照下文window端的服务器部分来写即可)。

window系统(虚拟机)

1、创建TCP服务

创建tcp服务进行侦听

void Mytcpserver::runserver(){// 创建 TCP 服务器server = new QTcpServer(this);// 连接新连接信号到槽函数connect(server, &QTcpServer::newConnection, this, &Mytcpserver::onNewConnection);// 绑定到 10001 端口if (!server->listen(QHostAddress::Any, 10001)) {qDebug() << "Server could not start!";} else {qDebug() << "Server started on port 10001.";}
}

2、接收打开应用程序命令,通过后台命令行启动进程

void Mytcpserver::onNewConnection() {QTcpSocket *socket = server->nextPendingConnection();// 获取对端的IP地址QHostAddress clientAddress = socket->peerAddress();//qDebug() << "New connection from:" << clientAddress.toString();m_peerIp=clientAddress.toString();// 连接读取数据信号connect(socket, &QTcpSocket::readyRead, [socket,this]() {netPackQ packet;qint64 bytesReceived = socket->read(reinterpret_cast<char*>(&packet), sizeof(netPackQ));if (bytesReceived == sizeof(netPackQ)) {// 处理接收到的数据qDebug() << "Received date";if(packet.opCode== 1){//执行后台命令,打开指定应用。比如:C:\Windows\notepad.exeqDebug() << "执行指定程序(PID|PATH). keyword:" << packet.keyword;bool bfind=false;QStringList recvlist=QString(packet.keyword).split("|");if(recvlist.size()>1){QString pid=recvlist[0];QString execpath=recvlist[1];//1.运行程序cmdrun act;act.Run(execpath);//2.获取execpath打开的窗口句柄,并进行绑定//因为执行程序之后,获取窗口句柄存在延时,需要加入轮询机制for(int num=0;num<5;num++){//给停止4秒的轮询机会qDebug() << "num:" << num;//QThread::sleep会导致QTimer定时器休眠,这里要主动调用getwinHwnd()QMap<HWND,QString> hwmdmap=getwinHwnd();//更新窗口句柄清单for (auto &key : hwmdmap.keys()) {HWND hwnd = key;QString hw_execpath = hwmdmap[key];//qDebug() << "hw_execpath:" << hw_execpath << "==>execpath:" << execpath;//if(hw_execpath==execpath && m_ignorehwndlist.count(hwnd)==0){//判断执行程序路径是否一样if(checkpath(hw_execpath,execpath) && m_ignorehwndlist.count(hwnd)==0){//判断执行程序路径是否一样//判断窗口是否已经绑定过,多一个执行程序是有可能开多个窗口的if(m_hwndpidmap.count(hwnd)==0){//如果没有绑定过就进行绑定操作stHwnd sthd;sthd.hwnd=hwnd;sthd.pid=pid;//sthd.path=execpath;sthd.path=hw_execpath;m_hwndpidmap[hwnd]=sthd;quintptr valueAsUInt = reinterpret_cast<quintptr>(hwnd);QString msg = QString("执行新程序 hwnd:%1==>pid:%2 path:%3").arg(valueAsUInt).arg(pid).arg(execpath);qDebug() << msg;writeLog(msg);bfind=true;break;}}}if(bfind) break;QThread::sleep(1);}}if(bfind)socket->write("sucess\n"); // 回复客户端--绑定成功elsesocket->write("fail\n"); // 回复客户端----绑定失败}} else {qDebug() << "Received incomplete data";}});// 连接断开信号connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
}

3、让指定应用窗口激活并指定显示到桌面

            else if(packet.opCode==2){//让指定的应用窗口置顶---接收宿主机的命令qDebug() << "执行窗口前置事件(PID). keyword:" << packet.keyword;bool bfind=false;QString pid=packet.keyword;for (auto &key : m_hwndpidmap.keys()){HWND hwnd = key;//QString hw_pid = m_hwndpidmap[hwnd];stHwnd sthd = m_hwndpidmap[hwnd];QString hw_pid = sthd.pid;if(hw_pid==pid){restoreNotepad(hwnd);bfind=true;}}if(bfind)socket->write("sucess\n"); // 回复客户端--找到pidelsesocket->write("fail\n"); // 回复客户端----未找到pid}
// 从任务栏恢复 Notepad 窗口
void Mytcpserver::restoreNotepad(HWND hwnd) {//qDebug() << __FUNCTION__ << __LINE__;qDebug() << __FUNCTION__ << "触发置顶事件操作";ShowWindow(hwnd, SW_RESTORE);  // 恢复窗口SetForegroundWindow(hwnd);      // 将窗口置于最前--只能从任务栏中激活,如果没有缩小到任务栏,无法展现在最前面SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);// 将窗口窗口置顶QThread::sleep(0.5);//有些应用需要延时才能达到置顶+取消置顶的效果,比如资源管理器SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);//取消窗口置顶
}

4、监测应用窗口关闭,同步发送信息给宿主机

        else if(packet.opCode == 9){//让指定的应用窗口关闭--接收宿主机的命令qDebug() << "关闭指定程序(PID). keyword:" << packet.keyword;bool bfind=false;QString pid=packet.keyword;for (auto &key : m_hwndpidmap.keys()){HWND hwnd = key;//QString hw_pid = m_hwndpidmap[hwnd];stHwnd sthd = m_hwndpidmap[hwnd];QString hw_pid = sthd.pid;if(hw_pid==pid){closeWindowByHwnd(hwnd);//通过窗口句柄关闭窗口时,如果需要保存,是无法直接关闭窗口的bfind=true;quintptr valueAsUInt = reinterpret_cast<quintptr>(hwnd);QString msg = QString("关闭指定程序(PID) hwnd:%1==>pid:%2").arg(valueAsUInt).arg(pid);writeLog(msg);}}if(bfind)socket->write("sucess\n"); // 回复客户端--找到pidelsesocket->write("fail\n"); // 回复客户端----未找到pid}
void Mytcpserver::checkkillproc(){QMap<HWND,QString> hwmdmap=getwinHwnd();for (auto &key : m_hwndpidmap.keys()) {stHwnd sthd = m_hwndpidmap[key];QString pid = sthd.pid;QString execpath = sthd.path;//句柄不存在,说明窗口被关闭了if(hwmdmap.count(key)==0){bool bret=false;bool bpro=false;for (auto &hkey : hwmdmap.keys()) {HWND hwnd = hkey;QString hw_execpath = hwmdmap[hwnd];//窗口关闭之后,如果还能匹配当当前开启的窗口的执行路径一样,并且不在过滤清单以及之前的记录中时,对句柄进行迁移(有些应用打开多个窗口)if(hw_execpath==execpath && m_ignorehwndlist.count(hwnd)==0 && m_hwndpidmap.count(hwnd)==0){//判断执行程序路径是否一样m_hwndpidmap.remove(key);sthd.hwnd=hwnd;m_hwndpidmap[hwnd]=sthd;bret=true;quintptr old_valueAsUInt = reinterpret_cast<quintptr>(key);quintptr valueAsUInt = reinterpret_cast<quintptr>(hwnd);QString msg = QString("句柄迁移old hwnd:%1==>new hwnd:%2").arg(old_valueAsUInt).arg(valueAsUInt);qDebug() << msg;writeLog(msg);break;}}if(bret==false && bpro==false){sendkillpid(pid);m_hwndpidmap.remove(key);quintptr valueAsUInt = reinterpret_cast<quintptr>(key);QString msg = QString("sendkillpid hwnd:%1==>pid:%2").arg(valueAsUInt).arg(pid);writeLog(msg);}}}//处理被忽略的窗口for (auto it = m_ignorehwndlist.begin(); it != m_ignorehwndlist.end(); ) {if(hwmdmap.count(*it)==0){qDebug() << "忽略名单中的" << *it << "已经被释放";it = m_ignorehwndlist.erase(it); // erase返回下一个有效的迭代器} else {++it;}}
}

设置定时器,及过滤已经打开的窗口

Mytcpserver::Mytcpserver(QObject *parent) : QObject(parent) {m_bfirst_status=true;getwinHwnd();//只取一次已经存在的窗口名单,这是被过滤的名单m_bfirst_status=false;qDebug() << "已经存在的窗口句柄:";QString msg="已经存在的窗口句柄:\n";for (const HWND &value : m_ignorehwndlist) {//std::cout << value << std::endl;qDebug() << "m_ignorehwndlist hwnd:" << value;// 转换 HWND 为 quintptrquintptr valueAsUInt = reinterpret_cast<quintptr>(value);stHwnd sthd=m_ignorehwndmap[value];msg += QString("%1 => %2 => %3\n").arg(valueAsUInt).arg(sthd.name).arg(sthd.path);}writeLog(msg);m_settinghwnd=0;addtimer = new QTimer(this);addtimer->setInterval(1000);connect(addtimer,&QTimer::timeout,this,&Mytcpserver::checkkillproc);addtimer->start();//m_peerIp="192.168.10.92";
}

结尾

        本篇主要是根据项目需求,提供关键的解决思路及方案,不方便提供全部代码,希望能帮到有相关需求场景的读者。

http://www.dtcms.com/wzjs/36987.html

相关文章:

  • 做网站镜像免费发布信息不收费的网站
  • 广告传媒公司简介内容网站关键词免费优化
  • 深圳市工程交易中心公众号seo排名
  • 网站设计网站项目流程图济南做seo排名
  • 怎么做一个网站云南十大营销案例分析
  • 什么网站做简历好关键词优化推广公司排名
  • 做外贸怎么连接国外网站如何做一个自己的网站呢
  • 公司企业网站模板百度竞价托管外包代运营
  • 怎么做网站设计程序免费建站系统
  • 支付网站搭建html简单网页设计作品
  • 怎样给网站做 站内搜索全网营销推广方式
  • 网店代运营代理免费广州seo
  • 博客做单页网站网页设计工作室长沙
  • 做爰全过程免费网站可以看营销公司取名字大全
  • 桥梁建设杂志网站网站买卖
  • 长春火车站和高铁站是一个站吗百度推广管理平台
  • 老干局网站建设方案百度seo查询系统
  • 自己做qq头像静态的网站seo关键词排名优化怎么收费
  • 合肥网站建设公司哪家好推广专员是做什么的
  • 二级域名分发网站源码关键词排名怎样
  • 做网站总结体会seo在哪学
  • 深圳b2b网站开发公司南宁网络推广热线
  • 营销型网站建设的特别之处都有哪些百度网址链接是多少
  • 如何做网站百度免费打开
  • pc网站建设是什么意思东莞优化seo
  • 协会网站建设厦门百度代理
  • 网站建设导向明确免费网站软件
  • 手机网站淘宝客怎么做seo建站优化推广
  • 骏域网站关键词点击优化工具
  • 做网站个人怎么签合同百度热议