├── image ├── win-Qt-demo.png └── Linux-Qt-demo.png ├── include ├── USBMonitor.h ├── win │ └── winUSBMonitor.h └── unix │ └── unixUSBMonitor.h ├── .gitignore ├── examples ├── usb-monitor.cpp └── Qt-usb-monitor.cpp ├── CMakeLists.txt ├── LICENSE ├── README.md └── docs └── build.md /image/win-Qt-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mq-b/USBMonitor-cpp/HEAD/image/win-Qt-demo.png -------------------------------------------------------------------------------- /image/Linux-Qt-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mq-b/USBMonitor-cpp/HEAD/image/Linux-Qt-demo.png -------------------------------------------------------------------------------- /include/USBMonitor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #include "win/winUSBMonitor.h" 5 | #elif defined(__linux__) 6 | #include "unix/unixUSBMonitor.h" 7 | #endif 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | /build 35 | /.vscode 36 | /.vs 37 | /.cache 38 | /out 39 | 40 | CMakeSettings.json 41 | CMakeUserPresets.json 42 | CMakePresets.json -------------------------------------------------------------------------------- /examples/usb-monitor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(){ 4 | USBMonitor monitor([](UsbState state, std::string path){ 5 | switch (state) { 6 | case UsbState::Removed: 7 | std::clog << "USB Removed: " << path << std::endl; 8 | break; 9 | case UsbState::Inserted: 10 | std::clog << "USB Inserted: " << path << std::endl; 11 | break; 12 | case UsbState::UpdateReady: 13 | std::clog << "USB Update Ready: " << path << std::endl; 14 | break; 15 | } 16 | }); 17 | monitor.startMonitoring(); 18 | std::cin.get(); 19 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(USBMonitor-cpp) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build/${CMAKE_BUILD_TYPE}/bin) 8 | 9 | set(CMAKE_AUTOMOC ON) 10 | 11 | file(GLOB EXAMPLE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/examples/*.cpp") 12 | 13 | find_package(Qt5 COMPONENTS Core Widgets REQUIRED) 14 | 15 | foreach(EXAMPLE_SOURCE IN ITEMS ${EXAMPLE_SOURCES}) 16 | get_filename_component(EXAMPLE_NAME ${EXAMPLE_SOURCE} NAME_WE) 17 | set(EXAMPLE_EXECUTABLE example-${EXAMPLE_NAME}) 18 | 19 | add_executable(${EXAMPLE_EXECUTABLE} ${EXAMPLE_SOURCE}) 20 | 21 | target_link_libraries(${EXAMPLE_EXECUTABLE} Qt5::Core Qt5::Widgets) 22 | 23 | target_include_directories(${EXAMPLE_EXECUTABLE} 24 | PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include 25 | ) 26 | endforeach() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 mq白 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # USBMonitor-cpp 2 | 3 | `USBMonitor-cpp` 是一个用 C++17 编写的开源项目,提供了一个用于实时监控 USB 设备插拔状态的库。 4 | 5 | 它十分的简单,通过开启一个线程时刻监控 Linux 中外部存储设备默认挂载路径来发送信号。 6 | 7 | 而 windows 中,它使用了一个 Windows API `GetDriveTypeA` 来获取当前所有驱动器进行监控。 8 | 9 | > [!NOTE] 10 | > `USBMonitor-cpp` 是一个**纯头文件库**,你只需要将 include 文件夹的内容复制到自己项目目录即可使用。 11 | 12 | ## 构建 13 | 14 | ```shell 15 | git clone https://github.com/Mq-b/USBMonitor-cpp 16 | cd USBMonitor-cpp 17 | mkdir build 18 | cd build 19 | cmake .. 20 | cmake --build . -j 21 | ``` 22 | 23 | 会在 `build` 目录下生成编译好的 `example` 可执行文件。 24 | 25 | 在 Ubuntu22.04 中使用 GCC11、GCC12、GCC13 测试无误。 26 | 27 | ![linux-qt-demo](./image/Linux-Qt-demo.png) 28 | 29 | 在 windows 使用 visual studio 17 测试无误。 30 | 31 | ![win-qt-demo](./image/win-Qt-demo.png) 32 | 33 | --- 34 | 35 | 如果需要如 wsl 之类的无桌面的 Linux 系统需要显式挂载外部存储设备进行测试,则可以这样: 36 | 37 | ```shell 38 | # 挂载外部存储这个 E 是 windows 中盘符 39 | mount -t drvfs E: /media/A/E 40 | 41 | # 手动拔出设备后解除挂载点 42 | sudo umount /media/A/E 43 | # 删除文件夹 44 | sudo rm -rf /media/A/E 45 | ``` 46 | 47 | ```txt 48 | root@Mq-B:~/test/USBMonitor-cpp/build/bin# sudo ./example-usb-monitor 49 | 插入更新U盘:/media/A/E 50 | USB Update Ready: /media/A/E 51 | 拔出U盘:/media/A/E 52 | USB Removed: /media/A/E 53 | ``` 54 | 55 | 如果是完整的带桌面的系统会自动处理挂载和卸载。 56 | -------------------------------------------------------------------------------- /examples/Qt-usb-monitor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | class UsbWatcher : public QObject { 7 | Q_OBJECT 8 | public: 9 | UsbWatcher() { 10 | monitor_ = std::make_unique( 11 | [this](UsbState s, std::string p) { 12 | emit usbEvent(s, QString::fromStdString(p)); 13 | } 14 | ); 15 | monitor_->startMonitoring(); 16 | } 17 | 18 | signals: 19 | void usbEvent(int state, const QString& path); 20 | 21 | public slots: 22 | void onUsbEvent(int state, const QString& path) { 23 | static const char* name[] = { "USB Removed", "USB Inserted", "USB Update Ready" }; 24 | QMessageBox::information(nullptr, name[state], path); 25 | } 26 | 27 | private: 28 | std::unique_ptr monitor_; 29 | }; 30 | 31 | int main(int argc, char* argv[]) { 32 | QApplication app(argc, argv); 33 | app.setQuitOnLastWindowClosed(false); 34 | UsbWatcher watcher; 35 | QObject::connect(&watcher, &UsbWatcher::usbEvent, &watcher, &UsbWatcher::onUsbEvent, Qt::QueuedConnection); 36 | return app.exec(); 37 | } 38 | 39 | #include "Qt-usb-monitor.moc" 40 | -------------------------------------------------------------------------------- /docs/build.md: -------------------------------------------------------------------------------- 1 | # build 2 | 3 | ## Linux 4 | 5 | Ubuntu22.04 使用 gcc13 + cmake + makefile 编译示例: 6 | 7 | ```bash 8 | mq-b@mqb-ubuntu:~/project$ git clone https://github.com/Mq-b/USBMonitor-cpp 9 | 正克隆到 'USBMonitor-cpp'... 10 | remote: Enumerating objects: 56, done. 11 | remote: Counting objects: 100% (56/56), done. 12 | remote: Compressing objects: 100% (34/34), done. 13 | remote: Total 56 (delta 17), reused 44 (delta 11), pack-reused 0 (from 0) 14 | 接收对象中: 100% (56/56), 13.07 KiB | 1.45 MiB/s, 完成. 15 | 处理 delta 中: 100% (17/17), 完成. 16 | mq-b@mqb-ubuntu:~/project$ cd USBMonitor-cpp/ 17 | mq-b@mqb-ubuntu:~/project/USBMonitor-cpp$ mkdir build 18 | mq-b@mqb-ubuntu:~/project/USBMonitor-cpp$ cd build 19 | mq-b@mqb-ubuntu:~/project/USBMonitor-cpp/build$ cmake .. 20 | -- The C compiler identification is GNU 13.1.0 21 | -- The CXX compiler identification is GNU 13.1.0 22 | -- Detecting C compiler ABI info 23 | -- Detecting C compiler ABI info - done 24 | -- Check for working C compiler: /usr/bin/gcc - skipped 25 | -- Detecting C compile features 26 | -- Detecting C compile features - done 27 | -- Detecting CXX compiler ABI info 28 | -- Detecting CXX compiler ABI info - done 29 | -- Check for working CXX compiler: /usr/bin/g++ - skipped 30 | -- Detecting CXX compile features 31 | -- Detecting CXX compile features - done 32 | -- Configuring done 33 | -- Generating done 34 | -- Build files have been written to: /home/mq-b/project/USBMonitor-cpp/build 35 | mq-b@mqb-ubuntu:~/project/USBMonitor-cpp/build$ cmake --build . -j 36 | [ 12%] Automatic MOC for target example-usb-monitor 37 | [ 25%] Automatic MOC for target example-Qt-usb-monitor 38 | [ 25%] Built target example-usb-monitor_autogen 39 | [ 37%] Building CXX object CMakeFiles/example-usb-monitor.dir/example-usb-monitor_autogen/mocs_compilation.cpp.o 40 | [ 50%] Building CXX object CMakeFiles/example-usb-monitor.dir/examples/usb-monitor.cpp.o 41 | [ 50%] Built target example-Qt-usb-monitor_autogen 42 | [ 62%] Building CXX object CMakeFiles/example-Qt-usb-monitor.dir/examples/Qt-usb-monitor.cpp.o 43 | [ 75%] Building CXX object CMakeFiles/example-Qt-usb-monitor.dir/example-Qt-usb-monitor_autogen/mocs_compilation.cpp.o 44 | [ 87%] Linking CXX executable bin/example-usb-monitor 45 | [ 87%] Built target example-usb-monitor 46 | [100%] Linking CXX executable bin/example-Qt-usb-monitor 47 | [100%] Built target example-Qt-usb-monitor 48 | mq-b@mqb-ubuntu:~/project/USBMonitor-cpp/build$ cd bin/ 49 | mq-b@mqb-ubuntu:~/project/USBMonitor-cpp/build/bin$ ls 50 | example-Qt-usb-monitor example-usb-monitor 51 | ``` 52 | 53 | 注意需要以 `sudo` 权限运行 `example`,否则无法访问 `/media` 目录。 54 | 55 | ![linux-qt-demo](./image/Linux-Qt-demo.png) 56 | 57 | ## Windows 58 | 59 | 在 Windows 中使用 Visual Studio 17 工具链测试无误。 60 | -------------------------------------------------------------------------------- /include/win/winUSBMonitor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std::chrono_literals; // NOLINT(clang-diagnostic-header-hygiene) 16 | using namespace std::string_literals; // NOLINT(clang-diagnostic-header-hygiene) 17 | 18 | enum UsbState { 19 | Removed, // U盘拔出 20 | Inserted, // U盘插入 21 | UpdateReady // 是一个更新U盘(有 update 文件夹且不为空) 22 | }; 23 | 24 | class USBMonitor { 25 | public: 26 | USBMonitor(std::function state) 27 | : USBstate{ std::move(state) } { 28 | } 29 | 30 | ~USBMonitor() { 31 | stop = true; 32 | if (usbMonitorThread.joinable()) 33 | usbMonitorThread.join(); 34 | } 35 | 36 | void startMonitoring() { 37 | usbMonitorThread = std::thread(&USBMonitor::monitorUSB, this); 38 | } 39 | 40 | private: 41 | bool isDirectoryNotEmpty(const std::filesystem::path& path) { 42 | // 检查路径是否存在且为目录 43 | if (std::filesystem::exists(path) && std::filesystem::is_directory(path)) { 44 | // 如果是目录,检查是否为空 45 | auto it = std::filesystem::directory_iterator(path); 46 | return it != std::filesystem::end(it); // 如果目录不为空,返回 true 47 | } 48 | return false; 49 | } 50 | 51 | bool IsRemovableDrive(char letter) { 52 | char rootPath[] = "A:\\"; 53 | rootPath[0] = letter; 54 | const UINT type = GetDriveTypeA(rootPath); 55 | return type == DRIVE_REMOVABLE; // 判断该盘符是否为可移动设备(U盘) 56 | } 57 | 58 | void monitorUSB() { 59 | while (!stop) { 60 | std::this_thread::sleep_for(100ms); 61 | findUSBMountPaths(); 62 | } 63 | } 64 | void findUSBMountPaths() { 65 | std::set newUSBPaths; 66 | const DWORD current = GetLogicalDrives(); // 返回一个位掩码,每一位代表一个盘符是否存在 67 | const std::bitset<26> currentDrives(current); 68 | 69 | for (int i = 0; i < 26; ++i) { 70 | if (const char driveLetter = 'A' + i; currentDrives[i] && IsRemovableDrive(driveLetter)) { 71 | newUSBPaths.insert(driveLetter + ":\\"s); // 记录当前已有磁盘路径 72 | } 73 | } 74 | 75 | // 比较新旧路径,如果有新的设备插入,则发送插入信号,反正有设备拔出则发送拔出信号 76 | // 检查新 USB 路径并发出插入信号 77 | for (const auto& usbPath : newUSBPaths) { 78 | if (std::lock_guard lc{ m }; currentUSBPaths.insert(usbPath).second) { // 如果是新路径,则插入并发送信号 79 | 80 | bool is_update = isDirectoryNotEmpty(usbPath + "/update"); 81 | 82 | if (is_update) { 83 | std::clog << "插入更新U盘:" << usbPath << '\n'; 84 | USBstate(UsbState::UpdateReady, usbPath); 85 | } 86 | else { 87 | std::clog << "插入U盘:" << usbPath << '\n'; 88 | USBstate(UsbState::Inserted, usbPath); 89 | } 90 | } 91 | } 92 | 93 | // 检查拔出路径并发出拔出信号 94 | for (const auto& usbPath : currentUSBPaths) { 95 | if (newUSBPaths.find(usbPath) == newUSBPaths.end()) { // 如果当前路径不在新路径中 96 | std::clog << "拔出U盘:" << usbPath << '\n'; 97 | USBstate(UsbState::Removed, usbPath); // 出U盘,发送 false 与拔出的设备路径 98 | } 99 | } 100 | 101 | std::lock_guard lc{ m }; 102 | currentUSBPaths = std::move(newUSBPaths); // 更新当前 USB 路径 103 | } 104 | 105 | std::set currentUSBPaths; // 记录当前已有磁盘路径 106 | mutable std::mutex m; 107 | std::atomic_bool stop = false; 108 | std::function USBstate; // 用于外部注入代码,发送插入与拔出的信号 109 | std::thread usbMonitorThread; 110 | }; 111 | -------------------------------------------------------------------------------- /include/unix/unixUSBMonitor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std::chrono_literals; // NOLINT(clang-diagnostic-header-hygiene) 15 | 16 | enum UsbState { 17 | Removed, // U盘拔出 18 | Inserted, // U盘插入 19 | UpdateReady // 是一个更新U盘(有 update 文件夹且不为空) 20 | }; 21 | 22 | class USBMonitor { 23 | public: 24 | USBMonitor(std::function state) 25 | : USBstate{ std::move(state) } { 26 | } 27 | 28 | ~USBMonitor() { 29 | stop = true; 30 | if (usbMonitorThread.joinable()) 31 | usbMonitorThread.join(); 32 | } 33 | 34 | void startMonitoring() { 35 | usbMonitorThread = std::thread(&USBMonitor::monitorUSB, this); 36 | } 37 | 38 | static bool isDirectoryNotEmpty(const std::filesystem::path& path) { 39 | // 检查路径是否存在且为目录 40 | if (std::filesystem::exists(path) && std::filesystem::is_directory(path)) { 41 | // 如果是目录,检查是否为空 42 | auto it = std::filesystem::directory_iterator(path); 43 | return it != std::filesystem::end(it); // 如果目录不为空,返回 true 44 | } 45 | return false; 46 | } 47 | static std::string escapePath(const std::string& path) { 48 | std::string result = path; 49 | size_t pos = 0; 50 | while ((pos = result.find(" ", pos)) != std::string::npos) { 51 | result.replace(pos, 1, "\\040"); 52 | pos += 4; // 跳过替换后的 "\040" 53 | } 54 | return result; 55 | } 56 | 57 | static bool isUSBMounted(const std::string& path) { 58 | std::string escapedPath = escapePath(path); 59 | 60 | std::ifstream mounts("/proc/mounts"); 61 | std::string line; 62 | while (std::getline(mounts, line)) { 63 | if (line.find(escapedPath) != std::string::npos) { 64 | return true; 65 | } 66 | } 67 | return false; 68 | } 69 | 70 | private: 71 | void monitorUSB() { 72 | while (!stop) { 73 | std::this_thread::sleep_for(100ms); 74 | findUSBMountPaths(); 75 | } 76 | } 77 | void findUSBMountPaths() { 78 | constexpr static std::string_view usbMountPath = "/media"; 79 | std::set newUSBPaths; 80 | 81 | for (const auto& entry : std::filesystem::directory_iterator{ usbMountPath }) { 82 | if (entry.is_directory()) { 83 | std::string userPath = entry.path().string(); 84 | 85 | for (const auto& usbEntry : std::filesystem::directory_iterator{ userPath }) { 86 | if (usbEntry.is_directory()) { 87 | newUSBPaths.insert(usbEntry.path().string()); 88 | } 89 | } 90 | } 91 | } 92 | // 检查新 USB 路径并发出插入信号 93 | for (const auto& usbPath : newUSBPaths) { 94 | if (std::lock_guard lc{ m }; currentUSBPaths.insert(usbPath).second) { // 如果是新路径,则插入并发送信号 95 | for(int i = 0; i < 5; i++){ 96 | if(isUSBMounted(usbPath)) 97 | break; 98 | std::this_thread::sleep_for(300ms); 99 | } 100 | 101 | bool is_update = isDirectoryNotEmpty(usbPath + "/update"); 102 | 103 | if (is_update) { 104 | std::clog << "插入更新U盘:" << usbPath << '\n'; 105 | USBstate(UsbState::UpdateReady, usbPath); 106 | } 107 | else { 108 | std::clog << "插入U盘:" << usbPath << '\n'; 109 | USBstate(UsbState::Inserted, usbPath); 110 | } 111 | } 112 | } 113 | 114 | // 检查拔出路径并发出拔出信号 115 | for (const auto& usbPath : currentUSBPaths) { 116 | if (newUSBPaths.find(usbPath) == newUSBPaths.end()) { // 如果当前路径不在新路径中 117 | std::clog << "拔出U盘:" << usbPath << '\n'; 118 | USBstate(UsbState::Removed, usbPath); // 出U盘,发送 false 与拔出的设备路径 119 | } 120 | } 121 | 122 | std::lock_guard lc{ m }; 123 | currentUSBPaths = std::move(newUSBPaths); // 更新当前 USB 路径 124 | } 125 | 126 | std::set currentUSBPaths; // 记录当前 USB 路径 127 | mutable std::mutex m; 128 | std::atomic_bool stop = false; 129 | std::function USBstate; // 用于外部注入代码,发送插入与拔出的信号 130 | std::thread usbMonitorThread; 131 | }; 132 | --------------------------------------------------------------------------------