Qt 监控USB设备的插入和移除
flyfish
Ubuntu22.04
Qt 6.2.4
CMakeLists.txt
内容
# 指定 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(USBMonitormain.cpp
)# 将 Qt Core 库和 udev 库链接到 USBMonitor 可执行文件
target_link_libraries(USBMonitor PRIVATE Qt${QT_VERSION_MAJOR}::Core udev)# 定义安装目标 USBMonitor 的安装路径
install(TARGETS USBMonitorLIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # 指定库文件的安装路径RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # 指定可执行文件的安装路径
)
当然,我可以详细解释 CMakeLists.txt
文件中的每一项以及它们在编译过程中的作用。
编译过程中的内部操作
-
解析
CMakeLists.txt
:- CMake 读取
CMakeLists.txt
文件,解析其中的指令和变量设置。
- CMake 读取
-
查找和配置依赖库:
find_package
命令会查找 Qt 库和libudev
库,并设置相应的变量,如Qt6::Core
和UDEV_LIBRARIES
。
-
生成构建文件:
- CMake 生成适合特定构建系统的文件,如 Makefile(对于 Unix 系统)或 Visual Studio 项目文件(对于 Windows 系统)。
-
编译源文件:
- 构建系统(如
make
)根据生成的构建文件编译源文件main.cpp
,生成目标文件main.cpp.o
。
- 构建系统(如
-
链接目标文件:
- 构建系统将目标文件
main.cpp.o
与Qt6::Core
和udev
库链接,生成最终的可执行文件USBMonitor
。
- 构建系统将目标文件
-
安装目标(可选):
- 如果运行
make install
命令,构建系统会将生成的可执行文件USBMonitor
安装到指定的目录,如/usr/local/bin
。
- 如果运行
源码
#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) {// 初始化 udevudev = 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
。 - 示例:
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
事件。 - 参数:
udev
:udev
上下文。name
:监视器的名称,通常为"udev"
。
- 返回值:成功时返回指向
udev_monitor
结构的指针,失败时返回NULL
。 - 示例:
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_monitor
:udev
监视器。subsystem
:要监听的子系统,例如"usb"
。devtype
:要监听的设备类型,可以为NULL
表示匹配所有设备类型。
- 返回值:成功时返回 0,失败时返回负数。
- 示例:
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_monitor
:udev
监视器。
- 返回值:成功时返回 0,失败时返回负数。
- 示例:
udev_monitor_enable_receiving(udev_monitor);
5. udev_monitor_get_fd()
- 原型:
int udev_monitor_get_fd(struct udev_monitor *udev_monitor);
- 作用:获取
udev
监视器的文件描述符,用于监听事件。 - 参数:
udev_monitor
:udev
监视器。
- 返回值:成功时返回文件描述符,失败时返回负数。
- 示例:
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_monitor
:udev
监视器。
- 返回值:成功时返回指向
udev_device
结构的指针,失败时返回NULL
。 - 示例:
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
。 - 示例:
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
。 - 示例:
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
。 - 示例:
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
:设备对象。
- 示例:
udev_device_unref(dev);
11. udev_monitor_unref()
- 原型:
void udev_monitor_unref(struct udev_monitor *udev_monitor);
- 作用:释放
udev_monitor
对象,减少其引用计数。 - 参数:
udev_monitor
:udev
监视器。
- 示例:
udev_monitor_unref(udev_monitor);
12. udev_unref()
- 原型:
void udev_unref(struct udev *udev);
- 作用:释放
udev
上下文对象,减少其引用计数。 - 参数:
udev
:udev
上下文。
- 示例:
udev_unref(udev);