usb-gadget
A Dart library for turning any Linux device with a USB controller into a USB peripheral — keyboard, mouse, storage, network adapter, audio device, or your own custom protocol.
Requires a device with a USB Device Controller (UDC). Embedded boards (Raspberry Pi 4/5, Zero 2 W) and Android phones with NDK support (e.g. OnePlus 7 Pro) work great; standard PCs typically don't have one.
dart pub add usb_gadget
Requirements
sudo apt-get install libaio-dev # async I/O library required by this package
sudo modprobe libcomposite # kernel module for USB gadget support
sudo mount -t configfs none /sys/kernel/config # if not already mounted
Example
import 'dart:io';
import 'dart:typed_data';
import 'package:usb_gadget/usb_gadget.dart';
class Keyboard extends HIDFunction {
Keyboard() : super(
name: 'keyboard',
descriptor: .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,
]),
protocol: 1,
subClass: 1,
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',
id: Id(vendor: 0x1234, product: 0x5679),
class_: .interfaceSpecific(),
strings: {
.enUS: const .new(
manufacturer: 'ACME Corp',
product: 'USB Keyboard',
serialnumber: 'KB001',
),
},
configs: [.new(description: 'A Keyboard', functions: [keyboard])],
);
final reg = await gadget.register();
try {
await reg.bind(defaultUDC);
await reg.udc?.awaitState(.configured);
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);
await ProcessSignal.sigint
.watch()
.first;
} finally {
await reg.bind(null);
await reg.remove();
}
}
Note
More examples (gamepad, mass storage, custom FunctionFS) are in example/.
Tip
Compile to a native executable for production use — JIT is not recommended for FunctionFS gadgets:
dart compile exe bin/app.dart -o my_gadget && sudo ./my_gadget
Enable debug logging at compile time:
dart compile exe \
-DUSB_GADGET_LOG=true \
-DUSB_GADGET_LOG_COLORS=true \
-DUSB_GADGET_LOG_LEVEL=0 \
bin/app.dart -o my_gadget
Kernel configuration
Most embedded distributions ship with the required options already enabled. To verify:
zcat /proc/config.gz | grep -E 'CONFIG_USB_(GADGET|CONFIGFS|FUNCTIONFS)'
Full list of kernel options
CONFIG_USB_GADGETCONFIG_USB_CONFIGFSCONFIG_USB_FUNCTIONFSCONFIG_USB_CONFIGFS_F_HIDCONFIG_USB_CONFIGFS_F_FSCONFIG_USB_CONFIGFS_MASS_STORAGECONFIG_USB_CONFIGFS_SERIALCONFIG_USB_CONFIGFS_ACMCONFIG_USB_CONFIGFS_NCMCONFIG_USB_CONFIGFS_ECMCONFIG_USB_CONFIGFS_ECM_SUBSETCONFIG_USB_CONFIGFS_RNDISCONFIG_USB_CONFIGFS_EEMCONFIG_USB_CONFIGFS_F_PRINTERCONFIG_USB_CONFIGFS_F_MIDICONFIG_USB_CONFIGFS_F_UAC1CONFIG_USB_CONFIGFS_F_UAC2CONFIG_USB_CONFIGFS_F_UVC
Android
No pre-built Dart SDK exists for Android — you must cross-compile both the SDK and libaio and
deploy to the device. The resulting binary runs from the Android command line only, with access to
dart:core APIs only.
Setup instructions
1. Build the Dart SDK for Android
Follow the official Dart SDK build guide,
then add "download_android_deps": True to custom_vars in your .gclient file and run:
gclient sync
./tools/build.py --mode=release --arch=arm64 --os=android create_sdk
adb push out/android/ReleaseAndroidARM64/dart-sdk /data/local/tmp/dart-sdk
2. Cross-compile libaio
git clone https://pagure.io/libaio.git && cd libaio
export TOOLCHAIN=<PATH>/dart-sdk/sdk/third_party/android_tools/ndk/toolchains/llvm/prebuilt/<ARCH>
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"
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 run on-device
adb push /path/to/your/project /data/local/tmp/project && adb shell
su
export PATH=/data/local/tmp/dart-sdk/bin:$PATH PUB_CACHE=/data/local/tmp/.pub-cache/
cd /data/local/tmp/project && dart pub get
dart compile exe bin/your_app.dart -o app && chmod +x app
# Detach 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
LD_LIBRARY_PATH=/data/local/tmp ./app
sudo ./app
setprop sys.usb.config "$ORIGINAL"
License
Apache 2.0 — see LICENSE.