usb_gadget 0.5.0
usb_gadget: ^0.5.0 copied to clipboard
Expose standard or fully custom USB peripherals (gadgets) through a USB device controller (UDC) on Linux.
usb-gadget #
Warning
This library is under active development. Breaking changes may occur without warning.
Turn any Linux device with a USB controller into a USB peripheral — keyboard, mouse, storage, network adapter, audio device, or your own custom protocol — written entirely in Dart.
What You Can Build #
Ready-Made Kernel Functions #
These work with zero protocol boilerplate — just configure and go.
| Category | Functions |
|---|---|
| Network | CDC ECM, ECM Subset, EEM, NCM, RNDIS |
| Serial | CDC ACM, Generic Serial |
| HID | Keyboards, mice, gamepads, custom devices |
| Storage | Mass Storage Device (USB flash drive emulation) |
| Audio/Video | MIDI, UAC1, UAC2, UVC (webcam) |
| Printer | USB Printer Class |
| Testing | Loopback, SourceSink |
Custom Functions with FunctionFS #
Need full control? FunctionFS lets you define your own USB descriptors, handle endpoints directly, implement proprietary protocols, and respond to USB lifecycle events (bind, unbind, enable, disable, suspend, resume).
The library includes HIDFunctionFs — a ready-made FunctionFS specialization for custom HID devices with complex report
descriptors.
Quick Start #
1. Check hardware compatibility #
You need a device with a USB Device Controller (UDC). Desktop PCs typically don't have one — embedded boards like the Raspberry Pi do.
ls /sys/class/udc/
# Should list at least one device, e.g.: fe980000.usb
Tested devices: Raspberry Pi 4/5, Raspberry Pi Zero 2 W, OnePlus 7 Pro (Android)
2. Install the system dependency #
# Debian/Ubuntu
sudo apt-get install libaio-dev
# Arch Linux
sudo pacman -S libaio
# Fedora/RHEL
sudo dnf install libaio-devel
3. Add the package #
dart pub add usb_gadget
4. Mount ConfigFS (if needed) #
# Check if already mounted
mount | grep configfs
# Mount if not present
sudo mount -t configfs none /sys/kernel/config
Example: USB Keyboard #
import 'dart:io';
import 'dart:typed_data';
import 'package:usb_gadget/usb_gadget.dart';
final _descriptor = Uint8List.fromList([0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65, 0x81, 0x00, 0xC0]);
class Keyboard extends HIDFunction {
Keyboard() : super(
name: 'keyboard',
descriptor: _descriptor,
protocol: .keyboard,
subclass: .boot,
reportLength: 8,
);
void sendKey(int keyCode, {int modifiers = 0}) =>
file..writeFromSync(
Uint8List(8)
..[0] = modifiers
..[2] = keyCode,
)..writeFromSync(Uint8List(8));
}
Future<void> main() async {
final keyboard = Keyboard();
final gadget = Gadget(
name: 'hid_keyboard',
idVendor: 0x1234,
idProduct: 0x5679,
deviceClass: .composite,
deviceSubClass: .none,
deviceProtocol: .none,
strings: {
.enUS: const .new(
manufacturer: 'ACME Corp',
product: 'USB Keyboard',
serialnumber: 'KB001',
),
},
config: .new(functions: [keyboard]),
);
try {
await gadget.bind();
await gadget.awaitState(.configured);
// A short delay prevents the first few keypresses from being missed on some hosts.
await Future<void>.delayed(const .new(milliseconds: 100));
[0x0B, 0x08, 0x0F, 0x0F, 0x12, 0x2C, 0x1A, 0x12, 0x15, 0x0F, 0x07, 0x28]
// Write "hello world\n"
.forEach(keyboard.sendKey);
stdout.writeln('Ctrl+C to exit.');
await ProcessSignal.sigint.watch().first;
} finally {
await gadget.unbind();
}
}
Run it #
# Development
sudo dart run bin/your_app.dart
# Compiled executable (recommended for deployment)
dart compile exe bin/your_app.dart -o my_gadget
sudo ./my_gadget
# With debug logging
dart compile exe -DUSB_GADGET_DEBUG=true bin/your_app.dart -o my_gadget
sudo ./my_gadget
More examples are available in the example/ directory.
Permissions #
Root or CAP_SYS_ADMIN is required to configure USB gadgets.
# Option A: run with sudo
sudo ./my_gadget
# Option B: grant capability to the binary (avoids full root)
sudo setcap cap_sys_admin+ep ./my_gadget
./my_gadget
Kernel Configuration #
Most modern embedded distributions already include the required options. To verify:
zcat /proc/config.gz | grep -E 'CONFIG_USB_(GADGET|CONFIGFS|FUNCTIONFS)'
Key options: CONFIG_USB_GADGET, CONFIG_USB_CONFIGFS, CONFIG_USB_FUNCTIONFS, plus any function-specific drivers you
need (e.g. CONFIG_USB_CONFIGFS_F_HID for HID).
To incorporate the official Android build documentation into your guide, I have refined the steps to ensure the
.gclient configuration is correct and added the necessary dependency installation step required for Linux hosts.
Android Support #
Important
Prerequisites: The host (build) machine must be an x64 Linux machine or a Mac. The target
Android device must support the Android NDK. The resulting Dart VM can only be run from the
Android command line and only has access to dart:core APIs — it does not have access to
Android C or Java APIs.
Because there are no pre-built Dart SDKs for Android, you must cross-compile the SDK and libaio
on your host machine before deploying to your device.
Expand Android setup instructions
1. Build the Dart SDK for Android #
First, follow the official Dart SDK build guide
to set up depot_tools and fetch the Dart source tree.
Then, use a text editor to add the download_android_deps variable to your .gclient file
(located in the directory where you ran fetch dart):
solutions = [
{
"name": "sdk",
"url": "https://dart.googlesource.com/sdk.git",
"deps_file": "DEPS",
"managed": False,
"custom_deps": {},
"custom_vars": {
"download_android_deps": True,
},
},
]
Then sync dependencies and build:
# Download Android NDK and SDK dependencies
gclient sync
# Build the full SDK for ARM64 Android
./tools/build.py --mode=release --arch=arm64 --os=android create_sdk
# Push the built SDK to your device
adb push out/android/ReleaseAndroidARM64/dart-sdk /data/local/tmp/dart-sdk
2. Cross-compile libaio #
libaio is required by this library but is not available via a package manager on Android. You
must cross-compile it on your host using the Clang toolchain bundled with the Dart SDK's
third-party dependencies.
git clone https://pagure.io/libaio.git && cd libaio
# Adjust the path below to match your Dart SDK checkout location
export TOOLCHAIN=$HOME/dart-sdk/sdk/third_party/android_tools/ndk/toolchains/llvm/prebuilt/linux-x86_64
export CC=$TOOLCHAIN/bin/aarch64-linux-android21-clang
export AR=$TOOLCHAIN/bin/llvm-ar
export RANLIB=$TOOLCHAIN/bin/llvm-ranlib
make clean && make CC="$CC" AR="$AR" RANLIB="$RANLIB"
# Deploy to device
adb push src/libaio.so.1.0.2 /data/local/tmp/
adb shell ln -s /data/local/tmp/libaio.so.1.0.2 /data/local/tmp/libaio.so
3. Compile and Execute On-Device #
# Upload your project
adb push /path/to/your/project /data/local/tmp/usb_gadget_project
adb shell
su
# Setup environment
export PATH=/data/local/tmp/dart-sdk/bin:$PATH
export PUB_CACHE=/data/local/tmp/.pub-cache/
cd /data/local/tmp/usb_gadget_project
# Fetch dependencies and compile
dart pub get
dart compile exe bin/your_app.dart -o usb_gadget_app
chmod +x usb_gadget_app
# Detach the UDC from the Android USB stack
ORIGINAL=$(getprop sys.usb.config)
setprop sys.usb.config none && sleep 1
mount -t configfs none /sys/kernel/config
# Run the app with the cross-compiled libaio
LD_LIBRARY_PATH=/data/local/tmp ./usb_gadget_app
# Cleanup: Restore USB config when finished
# setprop sys.usb.config "$ORIGINAL"