To get system notifications about events on USB devices, we are using a library called libudev. It provides only a plain C interface, and so we created simple C++ wrappers to make coding easier.
For our wrapper classes, we declared a namespace named usb:
namespace usb {
It contains two classes. The first class is Device, which gives us a C++ interface to a low-level libudev object called udev_device.
We defined a constructor that created an instance of Device from a udev_device pointer and a destructor to release the udev_device. Internally, libudev uses reference counting for its object, and so our destructor calls a function to decrease the reference count of the udev_device:
~Device() {
udev_device_unref(dev);
}
Device(const Device& other) : dev(other.dev) {
udev_device_ref(dev);
}
This way, we can copy the Device instances without memory or file descriptor leaks.
Besides the constructors and the destructor, the Device class has only two methods: action and attr. The action method returns the most recent USB device action:
std::string action() const {
return udev_device_get_action(dev);
}
The attr method returns any sysfs attribute associated with the device:
std::string attr(const char* name) const {
const char* val = udev_device_get_sysattr_value(dev,
name);
return val ? val : "";
}
The Monitor class also has a constructor and a destructor, but we made it noncopyable by disabling the copy constructor:
Monitor(const Monitor& other) = delete;
The constructor initializes the libudev instance using a static variable to ensure it is initialized only once:
struct udev* udev = udev_new();
It also sets up the monitoring filter and enables monitoring:
udev_monitor_filter_add_match_subsystem_devtype(
mon, "usb", NULL);
udev_monitor_enable_receiving(mon);
The wait method contains the most important monitoring logic. It accepts a function-like process object that is called each time an event is detected:
Device wait(std::function<bool(const Device&)> process) {
The function should return true if the event and the device it originates from are what we need; otherwise, it returns false to indicate that wait should keep working.
Internally, the wait function creates a file descriptor that is used to deliver device events to the program:
fds[0].fd = udev_monitor_get_fd(mon);
Then it sets up the monitoring loop. Despite its name, the poll function does not check the status of devices constantly; it waits for events on the specified file descriptors. We pass -1 as a timeout, indicating that we intend to wait for events forever:
int ret = poll(fds, 1, -1);
The poll function returns only in the case of an error or a new USB event. We handle an error condition by throwing an exception:
if (ret < 0) {
throw std::system_error(errno,
std::system_category(),
"Poll failed");
}
For each event, we create a new instance of Device and pass it to the process. If process returns true, we exit the wait loop, returning the instance of Device to the caller:
Device d(udev_monitor_receive_device(mon));
if (process(d)) {
return d;
};
Let's see how we can use these classes in our application. In the main function, we create an instance of Monitor and invoke its wait function. We use a lambda function to process each action:
usb::Device d = mon.wait([](auto& d) {
In the lambda function, we print information about all of the events:
std::cout << "Check [" << id << "] action: "
<< d.action() << std::endl;
We also check for the specific action and device id:
return d.action() == "bind" &&
id == "8086:0808";
Once found, we display information about its function and power requirements:
std::cout << d.attr("product")
<< " connected, uses up to "
<< d.attr("bMaxPower") << std::endl;
Running this application initially does not produce any output:
However, once we insert a USB device (a USB microphone in my case), we can see the following output:
The application can wait for a specific USB device and handle it after it is connected. It does this without busy looping, relying on the information provided by the operating system. As a result, the application spends most of the time sleeping while the poll call is blocked by the operating system.