linux桌面软件(wps)内嵌到主窗口后的关闭问题

news2024/11/25 9:38:20

程序测试环境是:slackware系统,属于linux系统,有桌面(Xface Session)。系统镜像是:slackware64-15.0-install-dvd.iso。qt、c++代码实现。

问题描述:延续上一篇文章,将wps软件窗口内嵌到qt的窗口后,出现一个棘手的问题,就是无法正常的关闭wps进程,即使表面上关闭了,再次打开并内嵌到主窗口时,会出现打不开wps软件的问题,会发现有wps的僵尸进程。

必要条件:slackware系统里需要安装wps、qt5开发工具,本篇文章不做详述。

程序编译:编译还是和上一篇文章保持一致。

我直接说问题的原因,当我们通过fork打开wps软件时,linux系统会出现两个关于该软件的wps进程(wpspdf),通过linux命令查看下进程信息如下:

/office6/wpspdf  这个进程就是打开pdf文件的软件wpspdf进程。

/bin/bash/ 这个进程是wpspdf进程的父进程。qt主窗口创建该进程,该进程又创建打开pdf的进程。

这俩进程都必须正常退出,才能保证我们下一次正确的再次打开软件。

第一种情况:当我们关闭了wps的qt父窗口,而不关闭wps软件界面(比如通过wps右上角的那个关闭按钮)时,虽然wps软件界面没了,但是后台的两个进程都没有关闭,这就导致wps的软件进程处于一种异常状态,这就导致下次打开时,发现完全打不开软件了。

第二种情况:如上图,当我们关闭了wps软件(比如通过wps右上角的那个关闭按钮),也关闭了其qt父窗口,这时我们linux命令查看进程信息会发现,wps进程剩了一个,而且还是个僵尸进程。其实就是/bin/bash/ 那个进程变成了僵尸进程。主要原因就是我们qt主窗口在创建该进程时,我们没有做wait操作,所以该进程没有正常退出,成为了僵尸进程。(上面两张图的进程号不一样的原因是,这是两次测试,所以id值不一样)。

以下是我修改后的最终代码:

form.h

#ifndef FORM_H
#define FORM_H

#include <QWidget>
#include <QProcess>

namespace Ui {
class Form;
}

class Form : public QWidget
{
    Q_OBJECT

public:
    explicit Form(QWidget *parent = nullptr);
    ~Form();

    void open_wps();

    static void open_wps_thread();

    void close_wps();

    void close_wps2();

    void find_window_id_in_tree();

    void find_window_id_by_class();

    void add_window_in_qt();

private:
    Ui::Form *ui;
    QProcess *process;
    uint32_t window_id;
};

#endif // FORM_H

form.cpp

#include "form.h"
#include "ui_form.h"
#include <QWindow>
#include <QVBoxLayout>
#include <QtX11Extras/QX11Info>
#include <xcb/xcb.h>
#include <QDebug>
#include <X11/Xlib.h> 
#include <X11/Xatom.h> 
#include <iostream>  
#include <vector>  
#include <string>  
#include <sstream>  
#include <fstream>
#include <unistd.h>
#include <sys/wait.h>
#include <thread>

Form::Form(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Form)
{
    ui->setupUi(this);
}

Form::~Form()
{
    delete ui;
    std::cout << "Form deleted" << std::endl;
}

void Form::open_wps_thread(){
    pid_t pid = fork();
    if(pid == 0){
        const char* wpsCommand[] = {"wpspdf", nullptr};//wps(word)、et(excel)、wpspdf(pdf)、wpp(ppt)
        std::cout << "in sub process:" << pid << std::endl;
        if(execvp(wpsCommand[0], (char* const*)wpsCommand) == -1){
            perror("failed to exec WPS");
        }
    }else if(pid > 0){        
        std::cout << "in main process:" << pid << std::endl;
        int status;
       waitpid(pid, &status, 0);
    }else{
        perror("fork failed");
    }    
}

// 打开默认软件
void Form::open_wps(){
   std::thread t(open_wps_thread);
   t.detach();
}

// 这个函数不太好,只关闭了窗口,没关闭进程
void Form::close_wps(){
 
    // 连接到X服务器
    xcb_connection_t *connection = xcb_connect(NULL, NULL);
    const xcb_setup_t *setup = xcb_get_setup(connection);
    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
    xcb_screen_t *screen = iter.data;
 
    // 创建一个cookie,用于请求服务器关闭窗口
    xcb_void_cookie_t cookie = xcb_destroy_window_checked(connection, window_id);
 
    // 等待请求完成
    xcb_generic_error_t *error = xcb_request_check(connection, cookie);
    if (error) {
        // 处理错误
        printf("Error code: %d\n", error->error_code);
        free(error);
    }
 
    // 断开连接
    xcb_disconnect(connection);
}

void Form::close_wps2(){
    Display *display = XOpenDisplay(NULL);
    if (!display) {
        fprintf(stderr, "Cannot open display.\n");
        return;
    }    

    // 创建一个临时窗口来获取默认的事件掩码
    Window root_return;
    int x_return, y_return;
    unsigned int width_return, height_return, border_width_return, depth_return;
    XGetGeometry(display, window_id, &root_return, &x_return, &y_return,
                 &width_return, &height_return, &border_width_return, &depth_return);

    // 发送 WM_DELETE_WINDOW 消息
    XEvent event;
    memset(&event, 0, sizeof(event));
    event.type = ClientMessage;
    event.xclient.display = display;
    event.xclient.window = window_id;
    event.xclient.message_type = XInternAtom(display, "WM_PROTOCOLS", False);
    event.xclient.format = 32;
    event.xclient.data.l[0] = XInternAtom(display, "WM_DELETE_WINDOW", False);
    event.xclient.data.l[1] = CurrentTime; // 当前时间戳

    XSendEvent(display, window_id, False, NoEventMask, &event);
    XFlush(display);

    XCloseDisplay(display);
}

// 通过系统窗口树形结构去一层层遍历窗口(我这里没有遍历子窗口,如果找不到某个窗口,或许是因为是子窗口)
void Form::find_window_id_in_tree()
{
    Display *display = XOpenDisplay(nullptr);
    if (!display) {
        std::cerr << "Cannot open display\n";
        return;
    }
    Window root = DefaultRootWindow(display);
    Window parent, *children;
    unsigned int num_children;

    if (!XQueryTree(display, root, &root, &parent, &children, &num_children)) {
        return;
    }

    std::vector<Window> windows;
    windows.push_back(root);
    for (unsigned int i = 0; i < num_children; ++i) {
        windows.push_back(children[i]);
    }
    XFree(children);

    for (Window win : windows) {
        char* name;
        int status = XFetchName(display, win, &name);
        if (status == 1) {
            // 这里的win就是窗口的id,也是窗口的句柄值find_window_id_by_class
            std::cout << "Found window name: " << name << " " << win << std::endl;
            if (std::string(name).find("wpspdf") != std::string::npos) {
//                XFree(name);
//                return;
            }
            XFree(name);
        }

        // 递归检查子窗口(如果有的话)
        // 注意:这里为了简化示例,没有实现递归
    }
}

// 将第三方软件(wps)窗口内嵌到qt窗口里
void Form::add_window_in_qt()
{
     QWindow *win = QWindow::fromWinId(window_id);
     QWidget *widget = QWidget::createWindowContainer(win);
     widget->setParent(this);
     QVBoxLayout *layout = new QVBoxLayout();
     layout->addWidget(widget);
     this->setLayout(layout);
}

// 获取窗口id(句柄)(找到的另外一种比较快捷的拿到窗口句柄的方式)
void Form::find_window_id_by_class()
{
    Display* display = XOpenDisplay(NULL);
    if (!display) {
        std::cerr << "Failed to open display" << std::endl;
        return;
    }

    Atom netClientListAtom = XInternAtom(display, "_NET_CLIENT_LIST", False);
    Atom actualType;
    int format;
    unsigned long numItems, bytesAfter;
    unsigned char* data = NULL;

    int status = XGetWindowProperty(display, DefaultRootWindow(display),
                                    netClientListAtom, 0, ~0UL, False,
                                    AnyPropertyType,
                                    &actualType, &format, &numItems, &bytesAfter,
                                    &data);


    if (status == Success && actualType == XA_WINDOW) {
        Window* windows = reinterpret_cast<Window*>(data);
        for (unsigned long i = 0; i < numItems; ++i) {
            Window win = windows[i];
            Atom actualType;
            int format;
            unsigned long nitems;
            unsigned long bytes_after;
            unsigned char* prop_data = nullptr;

            // 获取WM_CLASS属性
            if (XGetWindowProperty(display, win, XInternAtom(display, "WM_CLASS", False), 0, 1024, False, XA_STRING,
                                &actualType, &format, &nitems, &bytes_after, &prop_data) == Success) {
                std::string className(reinterpret_cast<char*>(prop_data));
                if (actualType == XA_STRING && className == "wpspdf") {
                    std::cout << "Window class name for window " << win << ": " << className << std::endl;
                    XFree(prop_data);
                    window_id = win;
                }
            }
        }
    } else {
        std::cerr << "Failed to get window list property" << std::endl;
    }

    if (data != NULL) {
        XFree(data);
    }

    XCloseDisplay(display);
}


1、重点关注 open_wps_thread线程函数,在该函数里,waitpid(pid, &status, 0)行代码,就是qt主窗口单起个新线程来创建和等待wps的进程,这样该进程就能正常结束了。

2、重点关注close_wps2函数,该函数可以通过窗口id来正常的关闭wps(wpspdf)软件进程。

 mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "form.h"
#include <unistd.h>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}



void MainWindow::on_pushButton_clicked()
{
    if(!f){
       f = new Form();
    }
    f->show();
    f->open_wps();
    sleep(2);
    f->find_window_id_by_class();
}


void MainWindow::on_pushButton_2_clicked()
{
    if(f){
        f->close_wps2();
    }
}

//
void MainWindow::on_pushButton_3_clicked()
{
    if(f){
        // f->showFullScreen(); // 全屏显示
        f->add_window_in_qt();
        // f->find_window_id_in_tree();
    }
}

 1、mainwindow.cpp也是对应到一个窗口,上面有三个按钮,分别用来调form.h的方法,用来测试的。先调用on_pushButton_clicked,再调用on_pushButton_3_clicked,最后调用on_pushButton_2_clicked就能测试出我们确实正确关闭了窗口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2193458.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中断系统的原理

一、介绍 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的。中断是指‌CPU在正常运行程序时&#xff0c;由于内部或外部事件的发生&#xff0c;导致CPU中断当前运行的程序&#xff0c;转而去执行其他程序的过程。‌ 中断可以是硬件产生的&#xff0c;也可以是…

神经网络激活函数列表大全及keras中的激活函数定义

一、概述 在机器学习中&#xff0c;激活函数是神经网络中的一种函数&#xff0c;用于在神经网络的每个神经元中引入非线性。没有激活函数&#xff0c;神经网络就无法学习复杂的模式&#xff0c;因为线性变换的组合仍然是线性的。 在神经网络的每层中&#xff0c;将该层所有输…

ElasticSearch备考 -- Multi match

一、题目 索引task有3个字段a、b、c&#xff0c;写一个查询去匹配这三个字段为mom&#xff0c;其中b的字段评分比a、c字段大一倍&#xff0c;将他们的分数相加作为最后的总分数 二、思考 通过题目要求对多个字段进行匹配查询&#xff0c;可以考虑multi match、bool query操作。…

计算机毕业设计 基于Python的人事管理系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

108页PPT丨OGSM战略规划框架:实现企业目标的系统化方法论

OGSM战略规划框架是一种实现企业目标的系统化方法论&#xff0c;它通过将组织的目标&#xff08;Objectives&#xff09;、目标&#xff08;Goals&#xff09;、策略&#xff08;Strategies&#xff09;和衡量指标&#xff08;Measures&#xff09;进行系统化整合&#xff0c;确…

Luminar财务造假风波:激光雷达龙头的困境与挑战

近日,美国激光雷达上市公司Luminar被爆出财务造假嫌疑,这一消息震惊了整个行业。Luminar,这家曾风光无限的激光雷达公司,最高市值一度达到120亿美元,其年轻的创始人也因此坐拥豪宅豪车无数。然而,如今在市值仅剩5亿美元左右的时候,却被爆出如此丑闻,令人不禁唏嘘。 带…

系统架构设计师-论文题(2021年下半年)

1.试题一 论面向方面的编程技术及其应用针对应用开发所面临的规模不断扩大、复杂度不断提升的问题&#xff0c;面向方面的编程Aspect Oriented Programming,AOP技术提供了一种有效的程序开发方法。为了理解和完成一个复杂的程序&#xff0c;通常要把程序进行功能划分和封装。一…

tcp/ip、以太网、mqtt、modbus/tcp复习

1.osi参考模型 2. modbus是应用层报文传输协议&#xff0c;没有规定物理层&#xff0c;只规定了协议帧&#xff0c;但是定义了控制器能够认识和使用的消息结构&#xff0c;不管它们是经过何种网络进行通信的&#xff0c;具有很强的适应性。 一主多从&#xff0c;同一时间主机…

Linux网络命令:如何查看linux系统防火墙开放的端口有哪些?多种方法来查看系统开放的网络端口号,包括TCP端口和UDP端口

目录 一、概述 二、查看防火墙开放的端口号的常用方法 &#xff08;一&#xff09;、使用firewalld&#xff08;适用于CentOS/RHEL 7&#xff09; 1. 查看firewalld状态 2. 查看所有开放的端口 3. 查看指定zone&#xff08;如public&#xff09;的开放端口 (二)、使用ipt…

基于SpringBoot+Vue+Uniapp的植物园管理小程序系统(2024最新,源码+文档+远程部署+讲解视频等)

3. 论文参考 4. 项目运行截图 5. 技术框架 5.1 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring …

眼儿媚​·秋雨绵绵窗暗暗

因患久治未愈的基础病 ——“肺气肿、哮喘、支扩吐血”等严重的呼吸道疾病&#xff0c;年近72岁的笔者于2017年初夏季节&#xff0c;情非得已地放弃半个多世纪习惯了的都市生活&#xff0c;来到成都市崇州市街子古镇寄居养病、养老&#xff0c;而今已逾七年。虽说一晃都是快要年…

【大模型 AI 学习】大模型 AI 部署硬件配置方案(本地硬件配置 | 在线GPU)

最近想部署一个开源深度学习项目&#xff0c;但是小编的笔记本电脑是8G的集成显存&#xff0c;且没有GPU&#xff0c;性能肯定是不够的。于是小编在小po站上粗浅了解了一下当前: 1. 大模型 AI本地硬件配置和 2. 云上申请GPU算力的两种方式。简单记录一下&#xff1a; 参考视频…

D29【python 接口自动化学习】- python基础之输入输出与文件操作

day29 格式化输出 学习日期&#xff1a;20241006 学习目标&#xff1a;输入输出与文件操作&#xfe63;-41 格式化输出&#xff1a;如何将执行结果通过屏幕输出&#xff1f; 学习笔记&#xff1a; 三种常用的格式化输出方式 百分号方式 format函数方式 总结 1. 格式化输出…

模拟实现消息队列(基于SpringBoot实现)

提要&#xff1a;此处的消息队列是仿照RabbitMQ实现&#xff08;参数之类的&#xff09;&#xff0c;实现一些基本的操作&#xff1a;创建/销毁交互机&#xff08;exchangeDeclare&#xff0c;exchangeDelete&#xff09;&#xff0c;队列&#xff08;queueDeclare&#xff0c;…

docker拉取镜像推送到阿里云镜像仓库

Docker拉取失败&#xff0c;利用github将镜像推送到阿里云 docker_image_pusher hub-mirror仓库 1、windows没有升级&#xff0c;用不了WSL。可以使用wsl&#xff0c;配合docker desktop直接拉取镜像&#xff0c;windows10安装WSL2及使用 2、开启安全surf the internet工具…

MQ 架构设计原理与消息中间件详解(三)

RabbitMQ实战解决方案 RabbitMQ死信队列 死信队列产生的背景 RabbitMQ死信队列俗称&#xff0c;备胎队列&#xff1b;消息中间件因为某种原因拒收该消息后&#xff0c;可以转移到死信队列中存放&#xff0c;死信队列也可以有交换机和路由key等。 产生死信队列的原因 消息投…

如何只修改obsidian图片链接为markdown

如何只修改obsidian图片链接为markdown 前言插件配置 使用注意 前言 适合有一定了解obsidian用法和插件市场&#xff0c;还有相对路径的人 插件 在obsidian插件市场搜索—开梯子 配置 首先使用ctrlp打开命令面板&#xff0c;也可以在左侧通过图标打开命令面板&#xff0c…

VTC视频时序控制器,TPG图像测试数据发生器,LCD驱动——FPGA学习笔记19

详情可以见LCD篇 LCD彩条显示——FPGA学习笔记10_依次显示红绿蓝三个通道-CSDN博客 一、VTC简介 Video Timing Controller 缩写 VTC 是我们在所有涉及 FPGA 图像、 FPGA 视频类方案中经常用到的一种用于产生视频时序的控制器。 本课以 VGA 为切入点&#xff0c; 学习视频传输…