• 【Wayland】QtWayland框架分析


    QtWayland框架分析

    QtWayland概念介绍

    QtWayland是Qt官方基于Wayland开发的一款Toolbox,根据其官网介绍

    The QtWayland module consists of two parts:
    Wayland platform plugin:
    Enables Qt applications to be run as Wayland clients.
    QtWaylandCompositor API:
    Enables the creation of Wayland compositors using Qt and QtQuick.

    可以将QtWayland的功能归纳为两点:

    1. 基于Qt/QtQuick,使用QtWayland可以写出一套 Compositor(Wayland Compositor)
    2. Qt应用可以作为Wayland的Client端运行。
    • 下图摘自Qt官网,对于Qt与Wayland关系的介绍。
      在这里插入图片描述 如何运行QtWayland?
    • Qtwayland是基于Qt编译的,可以参考QtWayland官网提供的编译手顺。并且QtWayland在其源码中,提供了一些Compositor的例子,在运行例子前只需要设置 如“QT_WAYLAND_HARDWARE_INTEGRATION”,“XDG_RUNTIME_DIR ”等环境变量即可(这里不在详细描述)

    QtWayland源码分析

    本文基于QtWayland 6.4的源码,对其模块框架及Compositor启动流程进行分析。

    模块框架

    请添加图片描述

    • ClientApi:Qtwayland提供给Client端使用的API。这些Api大部分是对于Wayland协议的封装。这样做的好处,是将Wayland协议与Client端进行了隔离。
    • ServerApi:QtWayland提供给你Server端(即Compositor)使用的API。这些API也是对Wayland协议的封装。你可以基于这些API,写出一个Compositor。
    • Plugins:插件模块。从Shell角度考虑,存在多种Shell,比如xdg-shell、ivi-shell、wl-shell、fullscreen-shell,Plugins实现了多种Shell(Client端),Qt应用只需要在启动前设定 Shell有关的环境变量,在启动Qt应用时就会根据环境变量使用不同的Shell。除了Shell外,还包括HardWareIntegration,这是Server端和Client端使用的Buffer,也是根据设定的 环境变量进行加载。
    • Platform:除了QtWayland,QtBase中实现了一个叫 eglfs的platform使用的渲染方式。
    • Common:协议文件、Scanner(根据协议文件生成源代码的工具。)
    Compositor启动(Server)
    • QWaylandCompositor是启动的入口
    // src\compositor\compositor_api\qwaylandcompositor.h
    class Q_WAYLANDCOMPOSITOR_EXPORT QWaylandCompositor : public QWaylandObject
    {
        Q_OBJECT
        // 对应类 QWaylandCompositorPrivate 
        Q_DECLARE_PRIVATE(QWaylandCompositor)
        // ...
    public:
        QWaylandCompositor(QObject *parent = nullptr);
        ~QWaylandCompositor() override;
    }
    
    // src\compositor\compositor_api\qwaylandcompositor_p.h
    class Q_WAYLANDCOMPOSITOR_EXPORT QWaylandCompositorPrivate : public QObjectPrivate, public QtWaylandServer::wl_compositor, public QtWaylandServer::wl_subcompositor
    {
    public:
        QWaylandCompositorPrivate(QWaylandCompositor *compositor);
        ~QWaylandCompositorPrivate() override; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 创建QWaylandCompositor,调用其构造函数
    // new QWaylandCompositor(),因为它提供默认参数,所以可以不传参数
    QWaylandCompositor::QWaylandCompositor(QObject *parent)
        : QWaylandObject(*new QWaylandCompositorPrivate(this), parent)
    {
    }
    // new QWaylandCompositorPrivate
    QWaylandCompositorPrivate::QWaylandCompositorPrivate(QWaylandCompositor *compositor)
    {
        // ...
        // 创建Display(全局Display)
        if (!display) {
            display = wl_display_create();
            ownsDisplay = true;
        }
       // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 到这步,创建了全局的Display对象。上面的代码中可以看到 wl_compositor 这个对象。
    • 写Compositor(Server)时,创建QWaylandCompositor后,需要调用其create方式,进行Server端的初始化。
    /*
     * Initializes the QWaylandCompositor.
     * If you override this function in your subclass, be sure to call the base class implementation.
     */
    void QWaylandCompositor::create()
    {
        Q_D(QWaylandCompositor);
        // d 是QWaylandCompositorPrivate
        // 大部分初始化是在 QWaylandCompositorPrivate::init中实现
        d->preInit();
        d->init();
    }
    
    void QWaylandCompositorPrivate::init()
    {
        // 设置Server端Socket名字
        // Socket用来跟Client端进行协议通讯
        if (socket_name.isEmpty()) {
            const int socketArg = arguments.indexOf(QLatin1String("--wayland-socket-name"));
            if (socketArg != -1 && socketArg + 1 < arguments.size())
                socket_name = arguments.at(socketArg + 1).toLocal8Bit();
        }
        // 这里初始化了wl_compositor/wl_subcompositor协议
        // 这里会调用到wl_global_create,将全局对象发布到display上
        wl_compositor::init(display, 4);
        wl_subcompositor::init(display, 1);
        // 初始化shm
        wl_display_init_shm(display);
    	// 创建Server端Socket
    	// Socket用于跟Client端通信
        if (!socket_name.isEmpty()) {
            if (wl_display_add_socket(display, socket_name.constData()))
                qFatal("Fatal: Failed to open server socket: \"%s\". XDG_RUNTIME_DIR is: \"%s\"\n", socket_name.constData(), getenv("XDG_RUNTIME_DIR"));
        } else {
            const char *autoSocketName = wl_display_add_socket_auto(display);
            if (!autoSocketName)
                qFatal("Fatal: Failed to open default server socket. XDG_RUNTIME_DIR is: \"%s\"\n", getenv("XDG_RUNTIME_DIR"));
            socket_name = autoSocketName;
            emit q->socketNameChanged(socket_name);
        }
        // 拿到loop
        loop = wl_display_get_event_loop(display);
        int fd = wl_event_loop_get_fd(loop);
    	// 通过Qt的QSocketNotifier 监听fd,当有读事件时触发回调processWaylandEvents
        QSocketNotifier *sockNot = new QSocketNotifier(fd, QSocketNotifier::Read, q);
        QObject::connect(sockNot, SIGNAL(activated(QSocketDescriptor)), q, SLOT(processWaylandEvents()));
    
        QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::eventDispatcher;
        QObject::connect(dispatcher, SIGNAL(aboutToBlock()), q, SLOT(processWaylandEvents()));
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 上述部分,完成了全局Display创建、Socket创建、wl_compositor这个重要协议的初始化,监听Socket的事件并绑定事件处理函数。Compositor端初始化的主要工作到这里完成。
  • 相关阅读:
    esp32系列(5):esp32 蓝牙架构学习
    密码学系列1-安全规约
    ARouter出现 there‘s no route matched in group问题排查
    量子计算机从澳大利亚实验室崛起会对加密货币产生威胁?
    天然气网络潮流计算模型研究(Matlab代码实现)
    Spring源码相关
    【前端】css如何实现箭头>?
    【Linux】-文件操作(重定向、缓冲区以及Linux下一切皆文件的详解)
    百度收录量查询易语言代码
    吴恩达机器学习课程笔记二
  • 原文地址:https://blog.csdn.net/zxc024000/article/details/127953543