Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions hid/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ description: Demonstrates how to use the hid plugin.
publish_to: "none" # Remove this line if you wish to publish to pub.dev

environment:
sdk: ">=2.15.1 <3.0.0"
sdk: '>=3.0.0 <4.0.0'
flutter: ">=3.10.0"

dependencies:
flutter:
Expand All @@ -14,7 +15,7 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
flutter_lints: ^3.0.1

# From a real application you should not have this
dependency_overrides:
Expand All @@ -28,6 +29,8 @@ dependency_overrides:
path: ../../hid_macos
hid_windows:
path: ../../hid_windows
hid_platform_interface:
path: ../../hid_platform_interface

flutter:
uses-material-design: true
4 changes: 2 additions & 2 deletions hid/lib/hid.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import 'package:hid_platform_interface/hid_platform_interface.dart';
export 'package:hid_platform_interface/device.dart';

HidPlatform get _platform => HidPlatform.instance;
Future<List<Device>> getDeviceList() {
return _platform.getDeviceList();
Future<List<Device>> getDeviceList({int? vendorId, int? productId}) {
return _platform.getDeviceList(vendorId: vendorId, productId: productId);
}
19 changes: 10 additions & 9 deletions hid/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
name: hid
description: A multi-platform plugin which allows an application to interface with USB HID-Class devices on Linux, Windows, Android, and macOS.
version: 0.1.6
version: 0.1.7
repository: https://github.com/rustui/hid/tree/main/hid

environment:
sdk: ">=2.15.0 <3.0.0"
flutter: ">=2.5.0"
sdk: '>=3.0.0 <4.0.0'
flutter: ">=3.10.0"

dependencies:
flutter:
sdk: flutter
hid_platform_interface: ^0.0.8
hid_windows: ^0.1.0
hid_macos: ^0.1.1
hid_linux: ^0.1.0
hid_android: ^0.1.3
ffi: ^2.1.0
hid_platform_interface: ^0.0.9
hid_windows: ^0.1.1
hid_macos: ^0.1.2
hid_linux: ^0.1.1
hid_android: ^0.1.4

dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
flutter_lints: ^3.0.1

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import kotlin.experimental.and

private const val REQUEST_GET_REPORT = 0x01;
private const val REQUEST_SET_REPORT = 0x09;
private const val REPORT_TYPE_INPUT = 0x0100;
private const val REPORT_TYPE_OUTPUT = 0x0200;
private const val REPORT_TYPE_FEATURE = 0x0300;

/** HidAndroidPlugin */
class HidAndroidPlugin : FlutterPlugin, MethodCallHandler {
Expand All @@ -26,8 +33,6 @@ class HidAndroidPlugin : FlutterPlugin, MethodCallHandler {
private val gson = Gson()
private var connection: UsbDeviceConnection? = null
private var device: UsbDevice? = null
private var interfaceIndex: Int? = null
private var endpointIndex: Int? = null

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "hid_android")
Expand All @@ -40,67 +45,215 @@ class HidAndroidPlugin : FlutterPlugin, MethodCallHandler {
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
"getDeviceList" -> {
val vendorId: Int? = call.argument("vendorId")
val productId: Int? = call.argument("productId")
val devices: MutableList<String> = mutableListOf()
for (device in usbManager.deviceList.values) {
try {
val json = gson.toJson(
HidDevice(
device.vendorId,
device.productId,
device.serialNumber ?: "",
device.productName ?: "",
device.deviceName
if (((vendorId == null) or (device.getVendorId() == (vendorId ?: 0))) and
((productId == null) or (device.getProductId() == (productId ?: 0)))) {
val json = gson.toJson(
HidDevice(
device.vendorId,
device.productId,
device.serialNumber ?: "",
device.productName ?: "",
device.deviceName
)
)
)
devices.add(json)
devices.add(json)
}
} catch (e: Exception) {
val permissionIntent =
PendingIntent.getBroadcast(
context,
0,
Intent("ACTION_USB_PERMISSION"),
0
PendingIntent.FLAG_IMMUTABLE
)
usbManager.requestPermission(device, permissionIntent)
}
}
result.success(devices)
}
"open" -> {
device = usbManager.deviceList[call.argument("deviceName")]!!
connection = usbManager.openDevice(device)
(interfaceIndex, endpointIndex) = getReadIndices(device!!)!!
result.success(
connection!!.claimInterface(
device!!.getInterface(interfaceIndex!!),
true
)
)
try {
device = usbManager.deviceList[call.argument("deviceName")]!!
connection = usbManager.openDevice(device)

result.success( true )
} catch (e: Exception) {
result.error("error", e.message, e.getStackTrace().joinToString(separator = "\n"))
}
}
"read" -> {
if (connection != null) {
val length: Int = call.argument("length")!!
val duration: Int = call.argument("duration")!!
Thread {
kotlin.run {
val array = ByteArray(length)
connection!!.bulkTransfer(
device!!.getInterface(i).getEndpoint(j),
array,
length,
duration
)
result.success(array.map { it.toUByte().toInt() })
try {
if (connection != null) {
val length: Int = call.argument("length")!!
val duration: Int = call.argument("duration")!!
val pair = getReadIndices(device!!)
if (pair == null) {
result.error("error", "Could not find interface and endpoint for read operation", call.method)
} else {
val interfaceIndex = pair.first;
val endpointIndex = pair.second;
if (!connection!!.claimInterface(device!!.getInterface(interfaceIndex!!), true)) {
result.error("error", "Could not claim interface for read operation", call.method)
} else {
Thread {
kotlin.run {
val array = ByteArray(length)
connection!!.bulkTransfer(
device!!.getInterface(interfaceIndex!!).getEndpoint(endpointIndex!!),
array,
length,
duration
)
result.success(array.map { it.toUByte().toInt() })
}
}.start()
}
}
}.start()
} else {
result.error("error", "error", "error")
} else {
result.error("error", "device connection is null", call.method)
}
} catch (e: Exception) {
result.error("error", e.message, e.getStackTrace().joinToString(separator = "\n"))
}
}
"write" -> {
try {
if (connection != null) {
val bytes: ByteArray = call.argument("bytes")!!
val pair = getWriteIndices(device!!)
if (pair == null) {
result.error("error", "Could not find interface and endpoint for write operation", call.method)
} else {
val interfaceIndex = pair.first;
val endpointIndex = pair.second;
if (!connection!!.claimInterface(device!!.getInterface(interfaceIndex!!), true)) {
result.error("error", "Could not claim interface for write operation", call.method)
} else {
Thread {
kotlin.run {
try {
connection!!.bulkTransfer(
device!!.getInterface(interfaceIndex!!).getEndpoint(endpointIndex!!),
bytes,
bytes.size,
1000
)
result.success(0)
} catch (e: Exception) {
result.error("error", e.message, e.getStackTrace().joinToString(separator = "\n"))
}
}
}.start()
}
}
} else {
result.error("error", "device connection is null", call.method)
}
} catch (e: Exception) {
result.error("error", e.message, e.getStackTrace().joinToString(separator = "\n"))
}
}
"setFeature" -> {
try {
if (connection != null) {
val bytes: ByteArray = call.argument("bytes")!!
val pair = getHIDIndices(device!!)
if (pair == null) {
result.error("error", "Could not find interface and endpoint for HID operation", call.method)
} else {
val interfaceIndex = pair.first;
val endpointIndex = pair.second;
if (!connection!!.claimInterface(device!!.getInterface(interfaceIndex!!), true)) {
result.error("error", "Could not claim interface for HID operation", call.method)
} else {
Thread {
kotlin.run {
try {
val reportId = bytes.get(0).toInt() and 0xff

connection!!.controlTransfer(
UsbConstants.USB_DIR_OUT or UsbConstants.USB_TYPE_CLASS or UsbConstants.USB_INTERFACE_SUBCLASS_BOOT,
REQUEST_SET_REPORT,
reportId or REPORT_TYPE_OUTPUT,
interfaceIndex ?: 0,
bytes,
1,
bytes.size - 1,
0
)
result.success(0)
} catch (e: Exception) {
result.error("error", e.message, e.getStackTrace().joinToString(separator = "\n"))
}
}
}.start()
}
}
} else {
result.error("error", "device connection is null", call.method)
}
} catch (e: Exception) {
result.error("error", e.message, e.getStackTrace().joinToString(separator = "\n"))
}
}
"getFeature" -> {
try {
if (connection != null) {
val bytes: ByteArray = call.argument("bytes")!!
val pair = getHIDIndices(device!!)
if (pair == null) {
result.error("error", "Could not find interface and endpoint for HID operation", call.method)
} else {
val interfaceIndex = pair.first;
val endpointIndex = pair.second;
if (!connection!!.claimInterface(device!!.getInterface(interfaceIndex!!), true)) {
result.error("error", "Could not claim interface for HID operation", call.method)
} else {
Thread {
kotlin.run {
try {
val reportId = bytes.get(0).toInt() and 0xff

val array = ByteArray(bytes.size - 1)

connection!!.controlTransfer(
UsbConstants.USB_DIR_IN or UsbConstants.USB_TYPE_CLASS or UsbConstants.USB_INTERFACE_SUBCLASS_BOOT,
REQUEST_GET_REPORT,
reportId or REPORT_TYPE_INPUT,
interfaceIndex ?: 0,
array,
bytes.size - 1,
0
)
result.success(array.map { it.toUByte().toInt() })
} catch (e: Exception) {
result.error("error", e.message, e.getStackTrace().joinToString(separator = "\n"))
}
}
}.start()
}
}
} else {
result.error("error", "device connection is null", call.method)
}
} catch (e: Exception) {
result.error("error", e.message, e.getStackTrace().joinToString(separator = "\n"))
}
}
"close" -> {
connection?.close()
connection = null
device = null
try {
connection?.close()
connection = null
device = null
result.success(0)
} catch (e: Exception) {
result.error("error", e.message, e.getStackTrace().joinToString(separator = "\n"))
}
}
else -> result.notImplemented()
}
Expand All @@ -122,4 +275,32 @@ fun getReadIndices(device: UsbDevice): Pair<Int, Int>? {
}
}
return null
}

fun getWriteIndices(device: UsbDevice): Pair<Int, Int>? {
for (i in 0 until device.interfaceCount) {
val inter = device.getInterface(i)
for (j in 0 until inter.endpointCount) {
val endpoint = inter.getEndpoint(j)
if (endpoint.type == UsbConstants.USB_ENDPOINT_XFER_INT && endpoint.direction == UsbConstants.USB_DIR_OUT) {
return Pair(i, j)
}
}
}
return null
}

fun getHIDIndices(device: UsbDevice): Pair<Int, Int>? {
for (i in 0 until device.interfaceCount) {
val inter = device.getInterface(i)
if (inter.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
for (j in 0 until inter.endpointCount) {
val endpoint = inter.getEndpoint(j)
if (endpoint.type == UsbConstants.USB_ENDPOINT_XFER_INT && endpoint.direction == UsbConstants.USB_DIR_IN) {
return Pair(i, j)
}
}
}
}
return null
}
Loading