zouni 0.2.1

Variety of functions that `std` does not offer or `std` offers but are not satisfiable
Documentation
use rusb::{Context, Device, Interfaces, Registration, UsbContext};
use serde::{Deserialize, Serialize};
use std::{
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc, LazyLock, Mutex,
    },
    time::Duration,
};

static LISTENER: LazyLock<Mutex<Listener>> = LazyLock::new(|| Mutex::new(Listener::default()));
static WATCHING: LazyLock<Arc<AtomicBool>> = LazyLock::new(|| Arc::new(AtomicBool::new(false)));

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceEvent {
    name: String,
    event: String,
}

struct HotPlugHandler {
    callback: Box<dyn FnMut(DeviceEvent) + 'static + Send>,
}

fn get_class_name(interfaces: Interfaces) -> String {
    let mut class_name = String::new();

    for interface in interfaces {
        for descriptor in interface.descriptors() {
            class_name = match descriptor.class_code() {
                1 => "Audio",
                2 => "COMM",
                3 => "HID",
                5 => "Physical",
                6 => "PTP",
                7 => "Printer",
                8 => "MassStorage",
                9 => "Hub",
                10 => "Data",
                _ => "Unknown",
            }
            .to_string();
        }
    }
    class_name
}

impl<T: UsbContext> rusb::Hotplug<T> for HotPlugHandler {
    fn device_arrived(&mut self, device: Device<T>) {
        (self.callback)(DeviceEvent {
            name: get_class_name(device.active_config_descriptor().unwrap().interfaces()),
            event: "Added".to_string(),
        });
    }

    fn device_left(&mut self, device: Device<T>) {
        (self.callback)(DeviceEvent {
            name: get_class_name(device.config_descriptor(0).unwrap().interfaces()),
            event: "Removed".to_string(),
        });
    }
}

#[derive(Default)]
struct Listener {
    context: Option<Context>,
    registration: Option<Registration<Context>>,
}

/// Get notification when any device is conneced
pub fn listen<F: FnMut(DeviceEvent) + 'static + Send>(callback: F) -> bool {
    if !rusb::has_hotplug() {
        return false;
    }

    if let Ok(context) = Context::new() {
        let callback = Box::new(callback);
        if let Ok(registration) = rusb::HotplugBuilder::new().register(
            &context,
            Box::new(HotPlugHandler {
                callback,
            }),
        ) {
            unlisten();

            *LISTENER.lock().unwrap() = Listener {
                context: Some(context),
                registration: Some(registration),
            };

            WATCHING.store(true, Ordering::SeqCst);

            std::thread::spawn(|| loop {
                if !WATCHING.load(Ordering::SeqCst) {
                    drop_context();
                    break;
                }

                if let Ok(listener) = LISTENER.try_lock() {
                    if let Some(context) = &listener.context {
                        context.handle_events(Some(Duration::from_millis(10))).unwrap();
                    }
                }
            });

            return true;
        }
    }

    false
}

fn drop_context() {
    if let Ok(mut con) = LISTENER.lock() {
        let context = con.context.take().unwrap();
        let registration = con.registration.take().unwrap();
        context.unregister_callback(registration);
    }
}

pub fn unlisten() {
    WATCHING.store(false, Ordering::SeqCst);
}

pub fn is_listening() -> bool {
    WATCHING.load(Ordering::SeqCst)
}