Qt 监控USB设备的插入和移除

Qt 监控USB设备的插入和移除

flyfish

Ubuntu22.04

Qt 6.2.4

CMakeLists.txt 内容

py 复制代码
# 指定 CMake 的最低版本要求
cmake_minimum_required(VERSION 3.16)

# 定义项目的名称和使用的编程语言
project(USBMonitor LANGUAGES CXX)

# 开启自动 UIC,MOC 和 RCC 工具
set(CMAKE_AUTOUIC ON)       # 自动运行 uic 工具处理 .ui 文件
set(CMAKE_AUTOMOC ON)       # 自动运行 moc 工具处理 Qt 的元对象系统
set(CMAKE_AUTORCC ON)       # 自动运行 rcc 工具处理资源文件

# 设置 C++ 标准为 C++17,并且要求编译器必须支持 C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找 Qt 库,并指定所需的组件
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core libudev)  # 尝试查找 Qt6 或 Qt5 库,并且要求 Core 和 libudev 组件
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)     # 根据找到的 Qt 版本(Qt6 或 Qt5),再次查找 Core 组件

# 包含 GNUInstallDirs 模块,该模块定义了一些标准的安装目录变量
include(GNUInstallDirs)

# 定义一个可执行目标 USBMonitor,并指定其源文件为 main.cpp
add_executable(USBMonitor
  main.cpp
)

# 将 Qt Core 库和 udev 库链接到 USBMonitor 可执行文件
target_link_libraries(USBMonitor PRIVATE Qt${QT_VERSION_MAJOR}::Core udev)

# 定义安装目标 USBMonitor 的安装路径
install(TARGETS USBMonitor
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}  # 指定库文件的安装路径
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}  # 指定可执行文件的安装路径
)

编译过程中的内部操作

  1. 解析 CMakeLists.txt

    • CMake 读取 CMakeLists.txt 文件,解析其中的指令和变量设置。
  2. 查找和配置依赖库

    • find_package 命令会查找 Qt 库和 libudev 库,并设置相应的变量,如 Qt6::CoreUDEV_LIBRARIES
  3. 生成构建文件

    • CMake 生成适合特定构建系统的文件,如 Makefile(对于 Unix 系统)或 Visual Studio 项目文件(对于 Windows 系统)。
  4. 编译源文件

    • 构建系统(如 make)根据生成的构建文件编译源文件 main.cpp,生成目标文件 main.cpp.o
  5. 链接目标文件

    • 构建系统将目标文件 main.cpp.oQt6::Coreudev 库链接,生成最终的可执行文件 USBMonitor
  6. 安装目标(可选):

    • 如果运行 make install 命令,构建系统会将生成的可执行文件 USBMonitor 安装到指定的目录,如 /usr/local/bin

源码

c 复制代码
#include <QCoreApplication>  // 包含 Qt 核心模块
#include <QSocketNotifier>   // 包含 QSocketNotifier 类
#include <QDebug>            // 包含调试输出功能
#include <unistd.h>          // 包含 POSIX 操作系统 API
#include <fcntl.h>           // 包含文件控制选项
#include <libudev.h>         // 包含 libudev 库
#include <sys/inotify.h>     // 包含 inotify API
#include <errno.h>           // 包含错误码定义

// 定义 UsbMonitor 类,继承自 QObject
class UsbMonitor : public QObject {
    Q_OBJECT  // 宏,用于 Qt 元对象系统

public:
    // 构造函数
    UsbMonitor(QObject *parent = nullptr) : QObject(parent) {
        // 初始化 udev
        udev = udev_new();  // 创建 udev 上下文
        if (!udev) {
            qCritical() << "无法初始化 udev";  // 如果初始化失败,输出错误信息
            return;
        }

        // 创建 udev 监视器
        udev_monitor = udev_monitor_new_from_netlink(udev, "udev");  // 创建 udev 监视器
        if (!udev_monitor) {
            qCritical() << "无法创建 udev 监视器";  // 如果创建失败,输出错误信息
            return;
        }

        // 过滤只监听 USB 设备
        udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", NULL);  // 过滤只监听 USB 子系统的设备
        udev_monitor_enable_receiving(udev_monitor);  // 启用接收事件

        // 获取 udev 监视器的文件描述符
        int udev_fd = udev_monitor_get_fd(udev_monitor);  // 获取 udev 监视器的文件描述符

        // 检查文件描述符是否有效
        if (fcntl(udev_fd, F_GETFD) == -1) {  // 使用 fcntl 检查文件描述符是否有效
            qCritical() << "无效的文件描述符:" << udev_fd;  // 如果无效,输出错误信息
            return;
        }

        // 创建 QSocketNotifier 并连接信号
        notifier = new QSocketNotifier(udev_fd, QSocketNotifier::Read, this);  // 创建 QSocketNotifier 对象
        connect(notifier, &QSocketNotifier::activated, this, &UsbMonitor::onUdevEvent);  // 连接 QSocketNotifier 的 activated 信号到 onUdevEvent 槽函数
    }

    // 析构函数
    ~UsbMonitor() {
        if (notifier) {
            delete notifier;  // 释放 QSocketNotifier 对象
        }
        if (udev_monitor) {
            udev_monitor_unref(udev_monitor);  // 释放 udev 监视器
        }
        if (udev) {
            udev_unref(udev);  // 释放 udev 上下文
        }
    }

private slots:
    // 处理 udev 事件的槽函数
    void onUdevEvent(int socket) {
        // 从 udev 监视器读取事件
        struct udev_device *dev = udev_monitor_receive_device(udev_monitor);  // 从 udev 监视器读取设备事件
        if (dev) {
            const char *action = udev_device_get_action(dev);  // 获取事件动作
            const char *devnode = udev_device_get_devnode(dev);  // 获取设备节点
            const char *devtype = udev_device_get_devtype(dev);  // 获取设备类型

            if (action && devnode && devtype) {
                if (strcmp(action, "add") == 0) {
                    qDebug() << "USB 设备插入:" << devnode;  // 如果是插入事件,输出设备节点
                } else if (strcmp(action, "remove") == 0) {
                    qDebug() << "USB 设备移除:" << devnode;  // 如果是移除事件,输出设备节点
                }
            }

            udev_device_unref(dev);  // 释放 udev_device 对象
        }
    }

private:
    struct udev *udev;  // udev 上下文
    struct udev_monitor *udev_monitor;  // udev 监视器
    QSocketNotifier *notifier;  // QSocketNotifier 对象
};

// 主函数
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);  // 创建 QCoreApplication 对象

    UsbMonitor monitor;  // 创建 UsbMonitor 对象

    return a.exec();  // 进入事件循环
}

#include "main.moc"  // 包含 moc 生成的代码

运行结果

USB 设备移除: /dev/bus/usb/002/006
USB 设备插入: /dev/bus/usb/002/007
USB 设备移除: /dev/bus/usb/002/007
USB 设备插入: /dev/bus/usb/002/008

#include "main.moc" 的主要作用是确保 moc(Meta-Object Compiler)生成的代码能够被编译器正确处理。具体来说,moc 会生成一些额外的代码,这些代码需要被包含在源文件的末尾,以便在编译时能够正确链接和使用。

Qt 元对象系统:

Qt 的元对象系统提供了信号和槽机制、运行时类型信息(RTTI)、动态属性系统等功能。

为了实现这些功能,Qt 提供了一个工具 moc(Meta-Object Compiler),它会生成一些额外的 C++ 代码。

moc 生成的代码:

当在类中使用 Q_OBJECT 宏时,moc 会为该类生成一些额外的代码,这些代码包括信号和槽的实现、元对象数据等。

生成的代码通常保存在一个与源文件同名的 .moc 文件中,例如 main.cpp 对应的 main.moc。

包含 main.moc:

为了确保生成的代码能够被编译器正确处理,需要在源文件的末尾包含 main.moc 文件。

这样做是为了确保 moc 生成的代码在编译时能够被正确链接到的源文件中。

编译顺序:

编译器在处理源文件时,会按照文件中出现的顺序依次处理每一行代码。

如果在源文件的开头包含 main.moc,可能会导致编译器在处理 moc 生成的代码时,还没有看到类的定义,从而引发编译错误。

因此,通常在源文件的末尾包含 main.moc,确保类的定义已经完全可见。

libudev 库提供的函数 ,用于管理和监控硬件设备的变化。

1. udev_new()

  • 原型struct udev *udev_new(void);

  • 作用 :创建一个新的 udev 上下文。

  • 返回值 :成功时返回指向 udev 结构的指针,失败时返回 NULL

  • 示例

    cpp 复制代码
    udev = udev_new();
    if (!udev) {
        qCritical() << "无法初始化 udev";
        return;
    }

2. udev_monitor_new_from_netlink()

  • 原型struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name);

  • 作用 :创建一个新的 udev 监视器,用于监听来自内核的 netlink 事件。

  • 参数

    • udevudev 上下文。
    • name:监视器的名称,通常为 "udev"
  • 返回值 :成功时返回指向 udev_monitor 结构的指针,失败时返回 NULL

  • 示例

    cpp 复制代码
    udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
    if (!udev_monitor) {
        qCritical() << "无法创建 udev 监视器";
        return;
    }

3. udev_monitor_filter_add_match_subsystem_devtype()

  • 原型int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype);

  • 作用 :为 udev 监视器添加一个过滤器,只监听特定子系统和设备类型的事件。

  • 参数

    • udev_monitorudev 监视器。
    • subsystem:要监听的子系统,例如 "usb"
    • devtype:要监听的设备类型,可以为 NULL 表示匹配所有设备类型。
  • 返回值:成功时返回 0,失败时返回负数。

  • 示例

    cpp 复制代码
    udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", NULL);

4. udev_monitor_enable_receiving()

  • 原型int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor);

  • 作用 :启用 udev 监视器,开始接收事件。

  • 参数

    • udev_monitorudev 监视器。
  • 返回值:成功时返回 0,失败时返回负数。

  • 示例

    cpp 复制代码
    udev_monitor_enable_receiving(udev_monitor);

5. udev_monitor_get_fd()

  • 原型int udev_monitor_get_fd(struct udev_monitor *udev_monitor);

  • 作用 :获取 udev 监视器的文件描述符,用于监听事件。

  • 参数

    • udev_monitorudev 监视器。
  • 返回值:成功时返回文件描述符,失败时返回负数。

  • 示例

    cpp 复制代码
    int udev_fd = udev_monitor_get_fd(udev_monitor);

6. udev_monitor_receive_device()

  • 原型struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor);

  • 作用 :从 udev 监视器中读取下一个设备事件。

  • 参数

    • udev_monitorudev 监视器。
  • 返回值 :成功时返回指向 udev_device 结构的指针,失败时返回 NULL

  • 示例

    cpp 复制代码
    struct udev_device *dev = udev_monitor_receive_device(udev_monitor);

7. udev_device_get_action()

  • 原型const char *udev_device_get_action(struct udev_device *udev_device);

  • 作用 :获取设备事件的动作(例如 "add""remove")。

  • 参数

    • udev_device:设备对象。
  • 返回值 :成功时返回动作字符串,失败时返回 NULL

  • 示例

    cpp 复制代码
    const char *action = udev_device_get_action(dev);

8. udev_device_get_devnode()

  • 原型const char *udev_device_get_devnode(struct udev_device *udev_device);

  • 作用 :获取设备的设备节点路径(例如 /dev/sdb1)。

  • 参数

    • udev_device:设备对象。
  • 返回值 :成功时返回设备节点路径字符串,失败时返回 NULL

  • 示例

    cpp 复制代码
    const char *devnode = udev_device_get_devnode(dev);

9. udev_device_get_devtype()

  • 原型const char *udev_device_get_devtype(struct udev_device *udev_device);

  • 作用 :获取设备的类型(例如 "disk""partition")。

  • 参数

    • udev_device:设备对象。
  • 返回值 :成功时返回设备类型字符串,失败时返回 NULL

  • 示例

    cpp 复制代码
    const char *devtype = udev_device_get_devtype(dev);

10. udev_device_unref()

  • 原型void udev_device_unref(struct udev_device *udev_device);

  • 作用 :释放 udev_device 对象,减少其引用计数。

  • 参数

    • udev_device:设备对象。
  • 示例

    cpp 复制代码
    udev_device_unref(dev);

11. udev_monitor_unref()

  • 原型void udev_monitor_unref(struct udev_monitor *udev_monitor);

  • 作用 :释放 udev_monitor 对象,减少其引用计数。

  • 参数

    • udev_monitorudev 监视器。
  • 示例

    cpp 复制代码
    udev_monitor_unref(udev_monitor);

12. udev_unref()

  • 原型void udev_unref(struct udev *udev);

  • 作用 :释放 udev 上下文对象,减少其引用计数。

  • 参数

    • udevudev 上下文。
  • 示例

    cpp 复制代码
    udev_unref(udev);
相关推荐
重生之我是数学王子2 小时前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
----云烟----11 小时前
QT中QString类的各种使用
开发语言·qt
「QT(C++)开发工程师」17 小时前
【qt版本概述】
开发语言·qt
一路冰雨21 小时前
Qt打开文件对话框选择文件之后弹出两次
开发语言·qt
老赵的博客21 小时前
QT 自定义界面布局要诀
开发语言·qt
码码哈哈0.01 天前
VSCode 2022 离线安装插件QT VSTOOl报错此扩展不能安装在任何当前安装的产品上。
ide·vscode·qt
feiyangqingyun1 天前
Qt/C++离线地图的加载和交互/可以离线使用/百度和天地图离线/支持手机上运行
c++·qt·qt天地图·qt离线地图·qt地图导航
gz94562 天前
windows下,用CMake编译qt项目,出现错误By not providing “FindQt5.cmake“...
开发语言·qt
「QT(C++)开发工程师」2 天前
Ubuntu 26.04 LTS 大升级:Qt 6 成为未来新引擎
qt
兆。2 天前
python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具
爬虫·python·qt