wdi_rs/lib.rs
1// Copyright (C) 2025 Piers Finlayson <piers@piers.rocks>
2//
3// MIT License
4
5//! # wdi-rs - Windows Driver Installer for Rust
6//!
7//! This crate provides a Rust API to [libwdi](https://github.com/pbatard/libwdi), the library behind [Zadiq](https://zadig.akeo.ie/), which provides simple Windows device driver installation. It is particularly useful for installing the WinUSB drivers for USB devices, allowing user-mode applications to communicate with them without needing to write a custom driver.
8//!
9//! As well as exposing the libwdi primitives, this crate exposes a higher level [`DriverInstaller`] builder API which simplifies common use cases such as:
10//! - installing a driver for a specific device by VID/PID
11//! - enumerating devices and selecting one based on custom criteria
12//! - using a custom INF file for driver installation, allowing more flexibility than the stock libwdi APIs.
13//!
14//! This is a Windows specific crate, and currently only targets x86 64-bit.
15//!
16//! ## Features
17//!
18//! - 🚀 High-level builder API for driver installation
19//! - 🔍 USB device enumeration and discovery
20//! - 📝 Custom INF file support (embedded or external)
21//! - 🛡️ Type-safe bindings to libwdi
22//! - 📋 Comprehensive logging support
23//! - ✅ x86 64-bit support
24//!
25//! ## Installation
26//!
27//! Add this to your `Cargo.toml`:
28//!
29//! ```toml
30//! [dependencies]
31//! wdi-rs = "0.1"
32//! ```
33//!
34//! ## Quick Start
35//!
36//! ### Install WinUSB driver for a specific device
37//!
38//! The simplest use case - install WinUSB for a device by its VID/PID:
39//!
40//! ```no_run
41//! use wdi_rs::DriverInstaller;
42//!
43//! fn main() -> Result<(), wdi_rs::Error> {
44//! // Install WinUSB driver for device 1234:5678
45//! // libwdi will automatically generate an appropriate INF file
46//! DriverInstaller::for_device(0x1234, 0x5678)
47//! .install()?;
48//!
49//! println!("Driver installed successfully!");
50//! Ok(())
51//! }
52//! ```
53//!
54//! ### Using a custom INF file
55//!
56//! For production use, you'll typically want to provide your own INF file, allowing you to custom more details of the driver installation:
57//!
58//! ```no_run
59//! use wdi_rs::DriverInstaller;
60//!
61//! // Embed your INF file at compile time
62//! const MY_DEVICE_INF: &[u8] = include_bytes!("..\\inf\\sample.inf");
63//!
64//! fn main() -> Result<(), wdi_rs::Error> {
65//! DriverInstaller::for_device(0x1234, 0x5678)
66//! .with_inf_data(MY_DEVICE_INF, "..\\inf\\sample.inf")
67//! .install()?;
68//!
69//! Ok(())
70//! }
71//! ```
72//!
73//! ### Enumerate devices before installation
74//!
75//! If you need to discover or select devices interactively:
76//!
77//! ```no_run
78//! use wdi_rs::{create_list, CreateListOptions, DriverInstaller};
79//!
80//! fn main() -> Result<(), wdi_rs::Error> {
81//! // Enumerate all USB devices
82//! let devices = create_list(CreateListOptions::default())?;
83//!
84//! // Find your specific device
85//! let device = devices.iter()
86//! .find(|d| d.vid == 0x1234 && d.pid == 0x5678)
87//! .ok_or(wdi_rs::Error::NotFound)?;
88//!
89//! println!("Found device: {}", device);
90//!
91//! // Install driver for this specific device
92//! DriverInstaller::for_specific_device(device.clone())
93//! .install()?;
94//!
95//! Ok(())
96//! }
97//! ```
98//!
99//! ### Advanced: Custom device selection
100//!
101//! For more complex device selection logic:
102//!
103//! ```no_run
104//! use wdi_rs::{DriverInstaller, DeviceSelector};
105//!
106//! fn main() -> Result<(), wdi_rs::Error> {
107//! // Install driver for the first device matching a custom predicate
108//! DriverInstaller::new(DeviceSelector::First(Box::new(|dev| {
109//! dev.vid == 0x1234 &&
110//! dev.desc.as_ref()
111//! .map_or(false, |desc| desc.contains("My Custom Device"))
112//! })))
113//! .install()?;
114//!
115//! Ok(())
116//! }
117//! ```
118//!
119//! ### Complete example with error handling and logging
120//!
121//! ```no_run
122//! use wdi_rs::{DriverInstaller, DriverType, Error};
123//! use log::{info, error};
124//!
125//! const MY_DEVICE_INF: &[u8] = include_bytes!("..\\inf\\sample.inf");
126//!
127//! fn install_driver(vid: u16, pid: u16) -> Result<(), Error> {
128//! info!("Starting driver installation for {:04x}:{:04x}", vid, pid);
129//!
130//! match DriverInstaller::for_device(vid, pid)
131//! .with_inf_data(MY_DEVICE_INF, "..\\inf\\sample.inf")
132//! .with_driver_type(DriverType::WinUsb)
133//! .install()
134//! {
135//! Ok(device) => {
136//! info!("Successfully installed driver for: {}", device);
137//! Ok(())
138//! }
139//! Err(Error::Exists) => {
140//! info!("Driver already installed");
141//! Ok(())
142//! }
143//! Err(Error::NotFound) => {
144//! error!("Device {:04x}:{:04x} not found", vid, pid);
145//! Err(Error::NotFound)
146//! }
147//! Err(e) => {
148//! error!("Failed to install driver: {}", e);
149//! Err(e)
150//! }
151//! }
152//! }
153//!
154//! fn main() {
155//! env_logger::init();
156//!
157//! if let Err(e) = install_driver(0x1234, 0x5678) {
158//! eprintln!("Installation failed: {}", e);
159//! std::process::exit(1);
160//! }
161//! }
162//! ```
163//!
164//! ## Low-Level API
165//!
166//! For cases where you need direct control, wdi-rs also exposes the low-level libwdi functions:
167//!
168//! ```no_run
169//! use wdi_rs::{create_list, prepare_driver, install_driver, CreateListOptions,
170//! PrepareDriverOptions, InstallDriverOptions, DriverType};
171//!
172//! fn main() -> Result<(), wdi_rs::Error> {
173//! // Get device list
174//! let devices = create_list(CreateListOptions {
175//! list_all: true,
176//! list_hubs: false,
177//! trim_whitespaces: true,
178//! })?;
179//!
180//! let device = &devices.get(0).ok_or(wdi_rs::Error::NotFound)?;
181//!
182//! // Prepare driver
183//! let mut prepare_opts = PrepareDriverOptions::default();
184//! prepare_opts.driver_type = DriverType::WinUsb;
185//!
186//! prepare_driver(
187//! device,
188//! "C:\\drivers",
189//! "C:\\drivers\\device.inf",
190//! &prepare_opts,
191//! )?;
192//!
193//! // Install driver
194//! install_driver(
195//! device,
196//! "C:\\drivers",
197//! "C:\\drivers\\device.inf",
198//! &InstallDriverOptions::default(),
199//! )?;
200//!
201//! Ok(())
202//! }
203//! ```
204//!
205//! ## Platform Support
206//!
207//! - **Windows 7+** (x64)
208//! - Requires administrator privileges for driver installation
209//! - This crate only compiles on Windows
210//!
211//! ## Architecture
212//!
213//! wdi-rs consists of two layers:
214//!
215//! 1. **Low-level FFI bindings** to libwdi (`prepare_driver`, `install_driver`, etc.)
216//! 2. **High-level API** with `DriverInstaller` builder pattern
217//!
218//! The high-level API is recommended for most use cases as it handles:
219//! - Device enumeration and selection
220//! - Temporary file management
221//! - INF file handling
222//! - Error propagation
223//! - Safe cleanup
224//!
225//! ## Common Use Cases
226//!
227//! ### Command-line tool for driver installation
228//!
229//! ```no_run
230//! use wdi_rs::DriverInstaller;
231//! use std::env;
232//!
233//! fn main() {
234//! let args: Vec<String> = env::args().collect();
235//! if args.len() != 3 {
236//! eprintln!("Usage: {} <VID> <PID>", args[0]);
237//! std::process::exit(1);
238//! }
239//!
240//! let vid = u16::from_str_radix(&args[1], 16).unwrap();
241//! let pid = u16::from_str_radix(&args[2], 16).unwrap();
242//!
243//! DriverInstaller::for_device(vid, pid)
244//! .install()
245//! .expect("Failed to install driver");
246//! }
247//! ```
248//!
249//! ### GUI application with device selection
250//!
251//! ```no_run
252//! use wdi_rs::{create_list, CreateListOptions, DriverInstaller};
253//!
254//! fn list_devices() -> Result<Vec<String>, wdi_rs::Error> {
255//! let devices = create_list(CreateListOptions::default())?;
256//! Ok(devices.iter()
257//! .map(|d| format!("{:04x}:{:04x} - {}", d.vid, d.pid,
258//! d.desc.as_deref().unwrap_or("Unknown")))
259//! .collect())
260//! }
261//!
262//! fn install_for_selected(vid: u16, pid: u16) -> Result<(), wdi_rs::Error> {
263//! DriverInstaller::for_device(vid, pid).install()?;
264//! Ok(())
265//! }
266//! ```
267//!
268//! ### Installer package
269//!
270//! Embed wdi-rs in your application's installer to automatically set up USB drivers:
271//!
272//! ```no_run
273//! use wdi_rs::DriverInstaller;
274//!
275//! const DEVICE_INF: &[u8] = include_bytes!("..\\inf\\sample.inf");
276//!
277//! fn setup_usb_driver() -> Result<(), Box<dyn std::error::Error>> {
278//! println!("Setting up USB driver...");
279//!
280//! DriverInstaller::for_device(0x1234, 0x5678)
281//! .with_inf_data(DEVICE_INF, "device.inf")
282//! .install()?;
283//!
284//! println!("USB driver installed successfully!");
285//! Ok(())
286//! }
287//! ```
288//!
289//! ## Logging
290//!
291//! wdi-rs uses the `log` crate for logging. Enable logging in your application:
292//!
293//! ```no_run
294//! use log::LevelFilter;
295//!
296//! fn main() {
297//! env_logger::Builder::from_default_env()
298//! .filter_level(LevelFilter::Info)
299//! .init();
300//!
301//! // Also set libwdi's log level
302//! wdi_rs::set_log_level(log::max_level().into()).ok();
303//!
304//! // Your code here...
305//! }
306//! ```
307//!
308//! ## Safety
309//!
310//! This crate uses unsafe code to interface with the libwdi C library. All unsafe code is carefully reviewed and encapsulated behind safe APIs. The high-level `DriverInstaller` API is entirely safe Rust.
311//!
312//! ## License
313//!
314//! MIT or Apache 2.0 License, at your option - see LICENSE file for details.
315//!
316//! ## Contributing
317//!
318//! Contributions are welcome! Please open an issue or PR on GitHub.
319//!
320//! ## Credits
321//!
322//! - [libwdi](https://github.com/pbatard/libwdi) by Pete Batard - the underlying C library
323//!
324//! ## See Also
325//!
326//! - [libwdi documentation](https://github.com/pbatard/libwdi/wiki)
327//! - [rusb](https://crates.io/crates/rusb) - USB library for Rust
328//! - [nusb](https://crates.io/crates/nusb) - Modern USB library for Rust
329
330#[cfg(any(target_os = "windows", doc))]
331mod ffi;
332#[cfg(any(target_os = "windows", doc))]
333mod installer;
334#[cfg(any(target_os = "windows", doc))]
335mod wdi;
336
337#[cfg(any(target_os = "windows", doc))]
338pub use installer::{DriverInstaller, DeviceSelector, InfSource, InstallOptions};
339#[cfg(any(target_os = "windows", doc))]
340pub use wdi::{
341 create_list, prepare_driver, install_driver,
342 CreateListOptions, Device, DeviceList, PrepareDriverOptions, InstallDriverOptions,
343 DriverType, Error, set_log_level,
344};
345
346#[cfg(all(not(target_os = "windows"), not(doc)))]
347compile_error!("This crate only supports Windows");