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} # 指定可执行文件的安装路径
)
编译过程中的内部操作
-
解析
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
。
- 如果运行
源码
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
。 -
示例 :
cppudev = 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
。 -
示例 :
cppudev_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,失败时返回负数。
-
示例 :
cppudev_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,失败时返回负数。
-
示例 :
cppudev_monitor_enable_receiving(udev_monitor);
5. udev_monitor_get_fd()
-
原型 :
int udev_monitor_get_fd(struct udev_monitor *udev_monitor);
-
作用 :获取
udev
监视器的文件描述符,用于监听事件。 -
参数 :
udev_monitor
:udev
监视器。
-
返回值:成功时返回文件描述符,失败时返回负数。
-
示例 :
cppint 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
。 -
示例 :
cppstruct 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
。 -
示例 :
cppconst 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
。 -
示例 :
cppconst 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
。 -
示例 :
cppconst char *devtype = udev_device_get_devtype(dev);
10. udev_device_unref()
-
原型 :
void udev_device_unref(struct udev_device *udev_device);
-
作用 :释放
udev_device
对象,减少其引用计数。 -
参数 :
udev_device
:设备对象。
-
示例 :
cppudev_device_unref(dev);
11. udev_monitor_unref()
-
原型 :
void udev_monitor_unref(struct udev_monitor *udev_monitor);
-
作用 :释放
udev_monitor
对象,减少其引用计数。 -
参数 :
udev_monitor
:udev
监视器。
-
示例 :
cppudev_monitor_unref(udev_monitor);
12. udev_unref()
-
原型 :
void udev_unref(struct udev *udev);
-
作用 :释放
udev
上下文对象,减少其引用计数。 -
参数 :
udev
:udev
上下文。
-
示例 :
cppudev_unref(udev);