Compare commits
2 commits
459f467bf3
...
2000160d35
Author | SHA1 | Date | |
---|---|---|---|
2000160d35 | |||
1c7f0e3554 |
8 changed files with 338 additions and 368 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
/.zig-cache
|
||||
/zig-cache
|
||||
/zig-out
|
||||
/result*
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
.{
|
||||
.name = "zig-hidapi",
|
||||
.name = .zig_hidapi,
|
||||
.version = "0.0.1",
|
||||
.paths = .{""},
|
||||
.fingerprint = 0x6b178366d6299650,
|
||||
.dependencies = .{},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
},
|
||||
}
|
||||
|
|
49
flake.lock
generated
49
flake.lock
generated
|
@ -1,21 +1,5 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
|
@ -36,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1734424634,
|
||||
"narHash": "sha256-cHar1vqHOOyC7f1+tVycPoWTfKIaqkoe1Q6TnKzuti4=",
|
||||
"lastModified": 1742422364,
|
||||
"narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d3c42f187194c26d9f0309a8ecc469d6c878ce33",
|
||||
"rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -52,8 +36,7 @@
|
|||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"zig": "zig"
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
|
@ -70,30 +53,6 @@
|
|||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"zig": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1734740962,
|
||||
"narHash": "sha256-5YsE/uxeHJSa1S86553fgjjYDRJl7iLs41cSSXW/K04=",
|
||||
"owner": "mitchellh",
|
||||
"repo": "zig-overlay",
|
||||
"rev": "956fa2f98490537625f582122b6f7f8adabf9686",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "mitchellh",
|
||||
"repo": "zig-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
14
flake.nix
14
flake.nix
|
@ -8,17 +8,11 @@
|
|||
flake-utils = {
|
||||
url = "github:numtide/flake-utils";
|
||||
};
|
||||
zig = {
|
||||
url = "github:mitchellh/zig-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
zig,
|
||||
...
|
||||
}: let
|
||||
in
|
||||
|
@ -31,8 +25,8 @@
|
|||
devShells.default = pkgs.mkShell {
|
||||
name = "zig-hidapi";
|
||||
nativeBuildInputs = [
|
||||
pkgs.zig_0_14
|
||||
pkgs.hidapi
|
||||
zig.packages.${system}.master
|
||||
];
|
||||
buildInputs = [
|
||||
pkgs.hidapi
|
||||
|
@ -41,12 +35,6 @@
|
|||
pkgs.hidapi
|
||||
];
|
||||
};
|
||||
packages.default = pkgs.zigStdenv.mkDerivation {
|
||||
pname = "zig-hidapi";
|
||||
version = "0.1.0";
|
||||
buildInputs = [pkgs.hidapi];
|
||||
src = ./.;
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
190
src/Device.zig
Normal file
190
src/Device.zig
Normal file
|
@ -0,0 +1,190 @@
|
|||
const Device = @This();
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const hidapi = @import("hidapi.zig");
|
||||
const DeviceInfo = @import("DeviceInfo.zig");
|
||||
|
||||
device: *hidapi.c.hid_device,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn open(vendor_id: c_ushort, product_id: c_ushort, serial_number: ?[]const u8) !Device {
|
||||
var buffer: [128]hidapi.c.wchar_t = undefined;
|
||||
const device = hidapi.c.hid_open(
|
||||
vendor_id,
|
||||
product_id,
|
||||
if (serial_number) |s| try hidapi.toWChar(s, &buffer) else null,
|
||||
);
|
||||
if (device) |d| return .{ .device = d };
|
||||
return error.HIDApiError;
|
||||
}
|
||||
|
||||
pub fn openPath(path: [:0]const u8) !Device {
|
||||
const device = hidapi.c.hid_open_path(path.ptr);
|
||||
if (device) |d| return .{ .device = d };
|
||||
return error.HIDApiError;
|
||||
}
|
||||
|
||||
pub fn close(self: Self) void {
|
||||
hidapi.c.hid_close(self.device);
|
||||
}
|
||||
|
||||
pub fn getVendorID(self: Self) !c_ushort {
|
||||
const di = hidapi.c.hid_get_device_info(self.device);
|
||||
return di.*.vendor_id;
|
||||
}
|
||||
|
||||
pub fn getProductID(self: Self) !c_ushort {
|
||||
const di = hidapi.c.hid_get_device_info(self.device);
|
||||
return di.*.product_id;
|
||||
}
|
||||
|
||||
pub fn getError(self: Self, alloc: std.mem.Allocator) !?[]const u8 {
|
||||
return try hidapi.fromWCharAlloc(alloc, hidapi.c.hid_error(self.device));
|
||||
}
|
||||
|
||||
/// Send a Feature report to the device.
|
||||
///
|
||||
/// Feature reports are sent over the Control endpoint as a Set_Report
|
||||
/// transfer. The first byte of `data` must contain the Report ID. For
|
||||
/// devices which only support a single report, this must be set to 0x0.
|
||||
/// The remaining bytes contain the report data. Since the Report ID is
|
||||
/// mandatory, calls to sendFeatureReport() will always contain one more
|
||||
/// byte than the report contains. For example, if a hid report is 16 bytes
|
||||
/// long, 17 bytes must be passed to hid_send_feature_report(): the Report
|
||||
/// ID (or 0x0, for devices which do not use numbered reports), followed by
|
||||
/// the report data (16 bytes). In this example, the length passed in would
|
||||
/// be 17.
|
||||
pub fn sendFeatureReport(self: Self, data: []const u8) !usize {
|
||||
const result = hidapi.c.hid_send_feature_report(self.device, data.ptr, data.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return @intCast(result);
|
||||
}
|
||||
|
||||
/// Get a feature report from a HID device.
|
||||
///
|
||||
/// Set the first byte of `data` to the Report ID of the report to be read.
|
||||
/// Make sure to allow space for this extra byte in `data`. Upon return, the
|
||||
/// first byte will still contain the Report ID, and the report data will
|
||||
/// start in data[1].
|
||||
pub fn getFeatureReport(self: Self, buffer: []u8) ![]const u8 {
|
||||
const result = hidapi.c.hid_get_feature_report(self.device, buffer.ptr, buffer.len);
|
||||
if (result < 0) return error.HIDAPiError;
|
||||
return buffer[0..@intCast(result)];
|
||||
}
|
||||
|
||||
/// Get a input report from a HID device.
|
||||
///
|
||||
/// Set the first byte of `data` to the Report ID of the report to be read.
|
||||
/// Make sure to allow space for this extra byte in `data`. Upon return, the
|
||||
/// first byte will still contain the Report ID, and the report data will
|
||||
/// start in `data[1]`.
|
||||
pub fn getInputReport(self: @This(), buffer: []u8) ![]const u8 {
|
||||
const result = hidapi.c.hid_get_input_report(self.device, buffer.ptr, buffer.len);
|
||||
if (result < 1) return error.HIDApiError;
|
||||
return buffer[0..@intCast(result)];
|
||||
}
|
||||
|
||||
/// Set the device handle to be non-blocking.
|
||||
///
|
||||
/// In non-blocking mode calls to read() will return immediately with a
|
||||
/// null if there is no data to be read. In blocking mode, read() will
|
||||
/// wait (block) until there is data to read before returning.
|
||||
///
|
||||
/// Nonblocking can be turned on and off at any time.
|
||||
pub fn setNonblocking(self: @This(), nonblocking: bool) !void {
|
||||
const result = hidapi.c.hid_set_nonblocking(self.device, if (nonblocking) 1 else 0);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
}
|
||||
|
||||
/// Write an Output report to a HID device.
|
||||
///
|
||||
/// The first byte of `data` must contain the Report ID. For
|
||||
/// devices which only support a single report, this must be set
|
||||
/// to 0x0. The remaining bytes contain the report data. Since
|
||||
/// the Report ID is mandatory, calls to `write()` will always
|
||||
/// contain one more byte than the report contains. For example,
|
||||
/// if a HID report is 16 bytes long, 17 bytes must be passed to
|
||||
/// `write()`, the Report ID (or 0x0, for devices with a
|
||||
/// single report), followed by the report data (16 bytes).
|
||||
///
|
||||
/// write() will send the data on the first OUT endpoint, if
|
||||
/// one exists. If it does not, it will send the data through
|
||||
/// the Control Endpoint (Endpoint 0).
|
||||
pub fn write(self: @This(), data: []const u8) !usize {
|
||||
const result = hidapi.c.hid_write(self.device, data.ptr, data.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return @intCast(result);
|
||||
}
|
||||
|
||||
/// Read an Input report from a HID device with timeout.
|
||||
///
|
||||
/// Input reports are returned to the host through the INTERRUPT IN endpoint.
|
||||
/// The first byte will contain the Report number if the device uses numbered
|
||||
/// reports.
|
||||
pub fn readTimeout(self: @This(), buffer: []u8, milliseconds: c_int) !?[]const u8 {
|
||||
const result = hidapi.c.hid_read_timeout(self.device, buffer.ptr, buffer.len, milliseconds);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
if (result == 0) return null;
|
||||
return buffer[0..result];
|
||||
}
|
||||
|
||||
/// Read an Input report from a HID device.
|
||||
///
|
||||
/// Input reports are returned to the host through the INTERRUPT IN endpoint.
|
||||
/// The first byte will contain the Report number if the device uses numbered
|
||||
/// reports.
|
||||
pub fn read(self: @This(), buffer: []u8) !?[]const u8 {
|
||||
const result = hidapi.c.hid_read(self.device, buffer.ptr, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
if (result == 0) return null;
|
||||
return buffer[0..@intCast(result)];
|
||||
}
|
||||
|
||||
/// Get The Manufacturer String from a HID device.
|
||||
pub fn getManufacturerString(self: @This(), alloc: std.mem.Allocator) ![]const u8 {
|
||||
var buffer: [128]hidapi.c.wchar_t = undefined;
|
||||
const result = hidapi.c.hid_get_manufacturer_string(self.device, &buffer, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return (try hidapi.fromWCharAlloc(alloc, &buffer)) orelse return error.HIDApiError;
|
||||
}
|
||||
|
||||
/// Get The Product String from a HID device.
|
||||
pub fn getProductString(self: @This(), alloc: std.mem.Allocator) ![]const u8 {
|
||||
var buffer: [128]hidapi.c.wchar_t = undefined;
|
||||
const result = hidapi.c.hid_get_product_string(self.device, &buffer, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return (try hidapi.fromWCharAlloc(alloc, &buffer)) orelse return error.HIDApiError;
|
||||
}
|
||||
|
||||
/// Get The Serial Number String from a HID device.
|
||||
pub fn getSerialNumberString(self: @This(), alloc: std.mem.Allocator) ![]const u8 {
|
||||
var buffer: [128]hidapi.c.wchar_t = undefined;
|
||||
const result = hidapi.c.hid_get_serial_number_string(self.device, &buffer, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return try hidapi.fromWCharAlloc(alloc, &buffer);
|
||||
}
|
||||
|
||||
/// Get a string from a HID device, based on its string index.
|
||||
pub fn getIndexedString(self: @This(), alloc: std.mem.Allocator, string_index: c_int) ![]const u8 {
|
||||
var buffer: [128]hidapi.c.wchar_t = undefined;
|
||||
const result = hidapi.c.hid_get_indexed_string(self.device, string_index, &buffer, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return try hidapi.fromWCharAlloc(alloc, &buffer);
|
||||
}
|
||||
|
||||
/// Get a report descriptor from a HID device.
|
||||
pub fn getReportDescriptor(self: @This(), alloc: std.mem.Allocator) ![]const u8 {
|
||||
var buffer: [hidapi.MAX_REPORT_DESCRIPTOR_SIZE]u8 = undefined;
|
||||
const result = hidapi.c.hid_get_report_descriptor(self.device, &buffer, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return try alloc.dupe(u8, buffer[0..@intCast(result)]);
|
||||
}
|
||||
|
||||
pub fn getDeviceInfo(self: @This(), alloc: std.mem.Allocator) !DeviceInfo {
|
||||
if (hidapi.c.hid_get_device_info(self.device)) |hid_device_info| {
|
||||
return DeviceInfo.init(alloc, hid_device_info);
|
||||
}
|
||||
return error.HIDApiError;
|
||||
}
|
63
src/DeviceInfo.zig
Normal file
63
src/DeviceInfo.zig
Normal file
|
@ -0,0 +1,63 @@
|
|||
const DeviceInfo = @This();
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const hidapi = @import("hidapi.zig");
|
||||
|
||||
vendor_id: c_ushort,
|
||||
product_id: c_ushort,
|
||||
path: []u8,
|
||||
serial_number: ?[]u8,
|
||||
release_number: c_ushort,
|
||||
manufacturer: ?[]u8,
|
||||
product: ?[]u8,
|
||||
usage_page: c_ushort,
|
||||
usage: c_ushort,
|
||||
interface_number: c_int,
|
||||
bus_type: hidapi.HidBusType,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, hid_device_info: [*c]hidapi.c.hid_device_info) !DeviceInfo {
|
||||
return .{
|
||||
.vendor_id = hid_device_info.*.vendor_id,
|
||||
.product_id = hid_device_info.*.product_id,
|
||||
.release_number = hid_device_info.*.release_number,
|
||||
.path = try alloc.dupe(u8, std.mem.span(hid_device_info.*.path)),
|
||||
.serial_number = try hidapi.fromWCharAlloc(alloc, hid_device_info.*.serial_number),
|
||||
.manufacturer = try hidapi.fromWCharAlloc(alloc, hid_device_info.*.manufacturer_string),
|
||||
.product = try hidapi.fromWCharAlloc(alloc, hid_device_info.*.product_string),
|
||||
.usage_page = hid_device_info.*.usage_page,
|
||||
.usage = hid_device_info.*.usage,
|
||||
.interface_number = hid_device_info.*.interface_number,
|
||||
.bus_type = @enumFromInt(hid_device_info.*.bus_type),
|
||||
};
|
||||
}
|
||||
|
||||
// pub fn open(self: Self) !Device {
|
||||
// const d = Device{
|
||||
// .device = hidapi.hid_open_path(self.path),
|
||||
// };
|
||||
// if (d.device) return d;
|
||||
// const err = hidapi.hid_error(null);
|
||||
// d.err = try from_wchar_alloc(err, &d.err);
|
||||
// return d;
|
||||
// }
|
||||
|
||||
pub fn format(self: *const DeviceInfo, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
try writer.print("{x:0>4} {x:0>4} {s} '{?s}'\n", .{ self.vendor_id, self.product_id, self.path, self.serial_number });
|
||||
try writer.print("Manufacturer: {?s}\n", .{self.manufacturer});
|
||||
try writer.print("Product: {?s}\n", .{self.product});
|
||||
try writer.print("Release: 0x{x}\n", .{self.release_number});
|
||||
try writer.print("Interface: 0x{x}\n", .{self.interface_number});
|
||||
try writer.print("Usage (page): 0x{x} (0x{x})\n", .{ self.usage, self.usage_page });
|
||||
try writer.print("Bus type: {} ({})\n", .{
|
||||
self.bus_type,
|
||||
@intFromEnum(self.bus_type),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *DeviceInfo, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.path);
|
||||
alloc.free(self.serial_number);
|
||||
alloc.free(self.manufacturer);
|
||||
alloc.free(self.product);
|
||||
}
|
21
src/DeviceInfoIterator.zig
Normal file
21
src/DeviceInfoIterator.zig
Normal file
|
@ -0,0 +1,21 @@
|
|||
const DeviceInfoIterator = @This();
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const hidapi = @import("hidapi.zig");
|
||||
const DeviceInfo = @import("DeviceInfo.zig");
|
||||
|
||||
start: ?*hidapi.c.hid_device_info,
|
||||
current: ?*hidapi.c.hid_device_info,
|
||||
|
||||
pub fn next(self: *DeviceInfoIterator, alloc: std.mem.Allocator) !?DeviceInfo {
|
||||
if (self.current) |current| {
|
||||
self.current = current.next;
|
||||
return try DeviceInfo.init(alloc, current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *DeviceInfoIterator) void {
|
||||
if (self.start) |start| hidapi.c.hid_free_enumeration(start);
|
||||
}
|
357
src/hidapi.zig
357
src/hidapi.zig
|
@ -1,131 +1,53 @@
|
|||
const std = @import("std");
|
||||
|
||||
const hidapi = @cImport({
|
||||
pub const c = @cImport({
|
||||
@cInclude("hidapi/hidapi.h");
|
||||
});
|
||||
|
||||
const Errors = error{
|
||||
HidApiInitError,
|
||||
HidApiExitError,
|
||||
};
|
||||
pub const Device = @import("Device.zig");
|
||||
pub const DeviceInfo = @import("DeviceInfo.zig");
|
||||
pub const DeviceInfoIterator = @import("DeviceInfoIterator.zig");
|
||||
|
||||
const MAX_REPORT_DESCRIPTOR_SIZE = hidapi.HID_API_MAX_REPORT_DESCRIPTOR_SIZE;
|
||||
pub const Errors = InitErrors || ExitErrors;
|
||||
|
||||
const HidBusType = enum(hidapi.hid_bus_type) {
|
||||
UNKNOWN = hidapi.HID_API_BUS_UNKNOWN,
|
||||
USB = hidapi.HID_API_BUS_USB,
|
||||
BLUETOOTH = hidapi.HID_API_BUS_BLUETOOTH,
|
||||
I2C = hidapi.HID_API_BUS_I2C,
|
||||
SPI = hidapi.HID_API_BUS_SPI,
|
||||
pub const MAX_REPORT_DESCRIPTOR_SIZE = c.HID_API_MAX_REPORT_DESCRIPTOR_SIZE;
|
||||
|
||||
pub const HidBusType = enum(c.hid_bus_type) {
|
||||
UNKNOWN = c.HID_API_BUS_UNKNOWN,
|
||||
USB = c.HID_API_BUS_USB,
|
||||
BLUETOOTH = c.HID_API_BUS_BLUETOOTH,
|
||||
I2C = c.HID_API_BUS_I2C,
|
||||
SPI = c.HID_API_BUS_SPI,
|
||||
_,
|
||||
};
|
||||
|
||||
pub const HidApiInitErrors = error{
|
||||
pub const InitErrors = error{
|
||||
HidApiInitError,
|
||||
};
|
||||
|
||||
pub fn init() HidApiInitErrors!void {
|
||||
const ret = hidapi.hid_init();
|
||||
pub fn init() InitErrors!void {
|
||||
const ret = c.hid_init();
|
||||
if (ret != 0) return error.HidApiInitError;
|
||||
}
|
||||
|
||||
pub const HidApiExitErrors = error{
|
||||
pub const ExitErrors = error{
|
||||
HidApiExitError,
|
||||
};
|
||||
|
||||
pub fn exit() HidApiExitErrors!void {
|
||||
const ret = hidapi.hid_exit();
|
||||
pub fn exit() ExitErrors!void {
|
||||
const ret = c.hid_exit();
|
||||
if (ret != 0) return error.HidApiExitError;
|
||||
}
|
||||
|
||||
pub fn getError(alloc: std.mem.Allocator) ![]const u8 {
|
||||
const err = try from_wchar_alloc(alloc, hidapi.hid_error(null));
|
||||
const err = try fromWCharAlloc(alloc, c.hid_error(null));
|
||||
if (err) |e| return e;
|
||||
return "";
|
||||
}
|
||||
|
||||
const DeviceInfo = struct {
|
||||
vendor_id: c_ushort,
|
||||
product_id: c_ushort,
|
||||
path: []u8,
|
||||
serial_number: ?[]u8,
|
||||
release_number: c_ushort,
|
||||
manufacturer: ?[]u8,
|
||||
product: ?[]u8,
|
||||
usage_page: c_ushort,
|
||||
usage: c_ushort,
|
||||
interface_number: c_int,
|
||||
bus_type: HidBusType,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, hid_device_info: [*c]hidapi.hid_device_info) !DeviceInfo {
|
||||
return .{
|
||||
.vendor_id = hid_device_info.*.vendor_id,
|
||||
.product_id = hid_device_info.*.product_id,
|
||||
.release_number = hid_device_info.*.release_number,
|
||||
.path = try alloc.dupe(u8, std.mem.span(hid_device_info.*.path)),
|
||||
.serial_number = try from_wchar_alloc(alloc, hid_device_info.*.serial_number),
|
||||
.manufacturer = try from_wchar_alloc(alloc, hid_device_info.*.manufacturer_string),
|
||||
.product = try from_wchar_alloc(alloc, hid_device_info.*.product_string),
|
||||
.usage_page = hid_device_info.*.usage_page,
|
||||
.usage = hid_device_info.*.usage,
|
||||
.interface_number = hid_device_info.*.interface_number,
|
||||
.bus_type = @enumFromInt(hid_device_info.*.bus_type),
|
||||
};
|
||||
}
|
||||
|
||||
// pub fn open(self: Self) !Device {
|
||||
// const d = Device{
|
||||
// .device = hidapi.hid_open_path(self.path),
|
||||
// };
|
||||
// if (d.device) return d;
|
||||
// const err = hidapi.hid_error(null);
|
||||
// d.err = try from_wchar_alloc(err, &d.err);
|
||||
// return d;
|
||||
// }
|
||||
|
||||
pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
try writer.print("{x:0>4} {x:0>4} {s} '{?s}'\n", .{ self.vendor_id, self.product_id, self.path, self.serial_number });
|
||||
try writer.print("Manufacturer: {?s}\n", .{self.manufacturer});
|
||||
try writer.print("Product: {?s}\n", .{self.product});
|
||||
try writer.print("Release: 0x{x}\n", .{self.release_number});
|
||||
try writer.print("Interface: 0x{x}\n", .{self.interface_number});
|
||||
try writer.print("Usage (page): 0x{x} (0x{x})\n", .{ self.usage, self.usage_page });
|
||||
try writer.print("Bus type: {} ({})\n", .{
|
||||
self.bus_type,
|
||||
@intFromEnum(self.bus_type),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: @This(), alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.path);
|
||||
alloc.free(self.serial_number);
|
||||
alloc.free(self.manufacturer);
|
||||
alloc.free(self.product);
|
||||
}
|
||||
};
|
||||
|
||||
pub const DeviceInfoIterator = struct {
|
||||
start: ?*hidapi.hid_device_info,
|
||||
current: ?*hidapi.hid_device_info,
|
||||
|
||||
pub fn next(self: *DeviceInfoIterator, alloc: std.mem.Allocator) !?DeviceInfo {
|
||||
if (self.current) |current| {
|
||||
self.current = current.next;
|
||||
return try DeviceInfo.init(alloc, current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *DeviceInfoIterator) void {
|
||||
if (self.start) |start| hidapi.hid_free_enumeration(start);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn enumerate(vendor_id: c_ushort, product_id_: ?c_ushort) !DeviceInfoIterator {
|
||||
const product_id = product_id_ orelse 0x0000;
|
||||
const device_info: ?*hidapi.hid_device_info = hidapi.hid_enumerate(vendor_id, product_id);
|
||||
const device_info: ?*c.hid_device_info = c.hid_enumerate(vendor_id, product_id);
|
||||
|
||||
return .{
|
||||
.start = device_info,
|
||||
|
@ -133,26 +55,30 @@ pub fn enumerate(vendor_id: c_ushort, product_id_: ?c_ushort) !DeviceInfoIterato
|
|||
};
|
||||
}
|
||||
|
||||
pub fn version() struct { major: c_int, minor: c_int, patch: c_int } {
|
||||
if (hidapi.hid_version()) |v| {
|
||||
pub fn version() std.SemanticVersion {
|
||||
if (c.hid_version()) |v| {
|
||||
return .{
|
||||
.major = v.*.major,
|
||||
.minor = v.*.minor,
|
||||
.patch = v.*.patch,
|
||||
.major = @intCast(v.*.major),
|
||||
.minor = @intCast(v.*.minor),
|
||||
.patch = @intCast(v.*.patch),
|
||||
};
|
||||
}
|
||||
return .{ .major = 0, .minor = 0, .patch = 0 };
|
||||
}
|
||||
|
||||
test "version-1" {
|
||||
const v = version();
|
||||
try std.testing.expectEqual(@as(i32, 0), v.major);
|
||||
try std.testing.expectEqual(@as(i32, 14), v.minor);
|
||||
try std.testing.expectEqual(@as(i32, 0), v.patch);
|
||||
try std.testing.expectEqual(
|
||||
std.SemanticVersion{
|
||||
.major = 0,
|
||||
.minor = 14,
|
||||
.patch = 0,
|
||||
},
|
||||
version(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn version_str() []const u8 {
|
||||
return std.mem.span(hidapi.hid_version_str());
|
||||
return std.mem.span(c.hid_version_str());
|
||||
}
|
||||
|
||||
test "version-2" {
|
||||
|
@ -160,218 +86,31 @@ test "version-2" {
|
|||
try std.testing.expectEqualStrings("0.14.0", v);
|
||||
}
|
||||
|
||||
pub const Device = struct {
|
||||
device: *hidapi.hid_device,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn open(vendor_id: c_ushort, product_id: c_ushort, serial_number: ?[]const u8) !Device {
|
||||
var buffer: [128]hidapi.wchar_t = undefined;
|
||||
const device = hidapi.hid_open(
|
||||
vendor_id,
|
||||
product_id,
|
||||
if (serial_number) |s| try to_wchar(s, &buffer) else null,
|
||||
);
|
||||
if (device) |d| return .{ .device = d };
|
||||
return error.HIDApiError;
|
||||
}
|
||||
|
||||
pub fn openPath(path: [:0]const u8) !Device {
|
||||
const device = hidapi.hid_open_path(path.ptr);
|
||||
if (device) |d| return .{ .device = d };
|
||||
return error.HIDApiError;
|
||||
}
|
||||
|
||||
pub fn close(self: Self) void {
|
||||
hidapi.hid_close(self.device);
|
||||
}
|
||||
|
||||
pub fn getVendorID(self: Self) !c_ushort {
|
||||
const di = hidapi.hid_get_device_info(self.device);
|
||||
return di.*.vendor_id;
|
||||
}
|
||||
|
||||
pub fn getProductID(self: Self) !c_ushort {
|
||||
const di = hidapi.hid_get_device_info(self.device);
|
||||
return di.*.product_id;
|
||||
}
|
||||
|
||||
pub fn getError(self: Self, alloc: std.mem.Allocator) ![]const u8 {
|
||||
const err = try from_wchar_alloc(alloc, hidapi.hid_error(self.device));
|
||||
if (err) |e| return e;
|
||||
return "";
|
||||
}
|
||||
|
||||
/// Send a Feature report to the device.
|
||||
///
|
||||
/// Feature reports are sent over the Control endpoint as a Set_Report
|
||||
/// transfer. The first byte of `data` must contain the Report ID. For
|
||||
/// devices which only support a single report, this must be set to 0x0.
|
||||
/// The remaining bytes contain the report data. Since the Report ID is
|
||||
/// mandatory, calls to sendFeatureReport() will always contain one more
|
||||
/// byte than the report contains. For example, if a hid report is 16 bytes
|
||||
/// long, 17 bytes must be passed to hid_send_feature_report(): the Report
|
||||
/// ID (or 0x0, for devices which do not use numbered reports), followed by
|
||||
/// the report data (16 bytes). In this example, the length passed in would
|
||||
/// be 17.
|
||||
pub fn sendFeatureReport(self: Self, data: []const u8) !usize {
|
||||
const result = hidapi.hid_send_feature_report(self.device, data.ptr, data.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return @intCast(result);
|
||||
}
|
||||
|
||||
/// Get a feature report from a HID device.
|
||||
///
|
||||
/// Set the first byte of `data` to the Report ID of the report to be read.
|
||||
/// Make sure to allow space for this extra byte in `data`. Upon return, the
|
||||
/// first byte will still contain the Report ID, and the report data will
|
||||
/// start in data[1].
|
||||
pub fn getFeatureReport(self: Self, buffer: []u8) ![]const u8 {
|
||||
const result = hidapi.hid_get_feature_report(self.device, buffer.ptr, buffer.len);
|
||||
if (result < 0) return error.HIDAPiError;
|
||||
return buffer[0..@intCast(result)];
|
||||
}
|
||||
|
||||
/// Get a input report from a HID device.
|
||||
///
|
||||
/// Set the first byte of `data` to the Report ID of the report to be read.
|
||||
/// Make sure to allow space for this extra byte in `data`. Upon return, the
|
||||
/// first byte will still contain the Report ID, and the report data will
|
||||
/// start in `data[1]`.
|
||||
pub fn getInputReport(self: @This(), buffer: []u8) ![]const u8 {
|
||||
const result = hidapi.hid_get_input_report(self.device, buffer.ptr, buffer.len);
|
||||
if (result < 1) return error.HIDApiError;
|
||||
return buffer[0..@intCast(result)];
|
||||
}
|
||||
|
||||
/// Set the device handle to be non-blocking.
|
||||
///
|
||||
/// In non-blocking mode calls to read() will return immediately with a
|
||||
/// null if there is no data to be read. In blocking mode, read() will
|
||||
/// wait (block) until there is data to read before returning.
|
||||
///
|
||||
/// Nonblocking can be turned on and off at any time.
|
||||
pub fn setNonblocking(self: @This(), nonblocking: bool) !void {
|
||||
const result = hidapi.hid_set_nonblocking(self.device, if (nonblocking) 1 else 0);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
}
|
||||
|
||||
/// Write an Output report to a HID device.
|
||||
///
|
||||
/// The first byte of `data` must contain the Report ID. For
|
||||
/// devices which only support a single report, this must be set
|
||||
/// to 0x0. The remaining bytes contain the report data. Since
|
||||
/// the Report ID is mandatory, calls to `write()` will always
|
||||
/// contain one more byte than the report contains. For example,
|
||||
/// if a HID report is 16 bytes long, 17 bytes must be passed to
|
||||
/// `write()`, the Report ID (or 0x0, for devices with a
|
||||
/// single report), followed by the report data (16 bytes).
|
||||
///
|
||||
/// write() will send the data on the first OUT endpoint, if
|
||||
/// one exists. If it does not, it will send the data through
|
||||
/// the Control Endpoint (Endpoint 0).
|
||||
pub fn write(self: @This(), data: []const u8) !usize {
|
||||
const result = hidapi.hid_write(self.device, data.ptr, data.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return @intCast(result);
|
||||
}
|
||||
|
||||
/// Read an Input report from a HID device with timeout.
|
||||
///
|
||||
/// Input reports are returned to the host through the INTERRUPT IN endpoint.
|
||||
/// The first byte will contain the Report number if the device uses numbered
|
||||
/// reports.
|
||||
pub fn readTimeout(self: @This(), buffer: []u8, milliseconds: c_int) !?[]const u8 {
|
||||
const result = hidapi.hid_read_timeout(self.device, buffer.ptr, buffer.len, milliseconds);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
if (result == 0) return null;
|
||||
return buffer[0..result];
|
||||
}
|
||||
|
||||
/// Read an Input report from a HID device.
|
||||
///
|
||||
/// Input reports are returned to the host through the INTERRUPT IN endpoint.
|
||||
/// The first byte will contain the Report number if the device uses numbered
|
||||
/// reports.
|
||||
pub fn read(self: @This(), buffer: []u8) !?[]const u8 {
|
||||
const result = hidapi.hid_read(self.device, buffer.ptr, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
if (result == 0) return null;
|
||||
return buffer[0..@intCast(result)];
|
||||
}
|
||||
|
||||
/// Get The Manufacturer String from a HID device.
|
||||
pub fn getManufacturerString(self: @This(), alloc: std.mem.Allocator) ![]const u8 {
|
||||
var buffer: [128]hidapi.wchar_t = undefined;
|
||||
const result = hidapi.hid_get_manufacturer_string(self.device, &buffer, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return (try from_wchar_alloc(alloc, &buffer)) orelse return error.HIDApiError;
|
||||
}
|
||||
|
||||
/// Get The Product String from a HID device.
|
||||
pub fn getProductString(self: @This(), alloc: std.mem.Allocator) ![]const u8 {
|
||||
var buffer: [128]hidapi.wchar_t = undefined;
|
||||
const result = hidapi.hid_get_product_string(self.device, &buffer, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return (try from_wchar_alloc(alloc, &buffer)) orelse return error.HIDApiError;
|
||||
}
|
||||
|
||||
/// Get The Serial Number String from a HID device.
|
||||
pub fn getSerialNumberString(self: @This(), alloc: std.mem.Allocator) ![]const u8 {
|
||||
var buffer: [128]hidapi.wchar_t = undefined;
|
||||
const result = hidapi.hid_get_serial_number_string(self.device, &buffer, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return try from_wchar_alloc(alloc, &buffer);
|
||||
}
|
||||
|
||||
/// Get a string from a HID device, based on its string index.
|
||||
pub fn getIndexedString(self: @This(), alloc: std.mem.Allocator, string_index: c_int) ![]const u8 {
|
||||
var buffer: [128]hidapi.wchar_t = undefined;
|
||||
const result = hidapi.hid_get_indexed_string(self.device, string_index, &buffer, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return try from_wchar_alloc(alloc, &buffer);
|
||||
}
|
||||
|
||||
/// Get a report descriptor from a HID device.
|
||||
pub fn getReportDescriptor(self: @This(), alloc: std.mem.Allocator) ![]const u8 {
|
||||
var buffer: [MAX_REPORT_DESCRIPTOR_SIZE]u8 = undefined;
|
||||
const result = hidapi.hid_get_report_descriptor(self.device, &buffer, buffer.len);
|
||||
if (result < 0) return error.HIDApiError;
|
||||
return try alloc.dupe(u8, buffer[0..@intCast(result)]);
|
||||
}
|
||||
|
||||
pub fn getDeviceInfo(self: @This(), alloc: std.mem.Allocator) !DeviceInfo {
|
||||
if (hidapi.hid_get_device_info(self.device)) |hid_device_info| {
|
||||
return DeviceInfo.init(alloc, hid_device_info);
|
||||
}
|
||||
return error.HIDApiError;
|
||||
}
|
||||
};
|
||||
|
||||
fn from_wchar_alloc(alloc: std.mem.Allocator, wide_string: [*c]const hidapi.wchar_t) !?[]u8 {
|
||||
pub fn fromWCharAlloc(alloc: std.mem.Allocator, wide_string: [*c]const c.wchar_t) !?[]u8 {
|
||||
if (wide_string == null) return null;
|
||||
var output = std.ArrayList(u8).init(alloc);
|
||||
var writer = output.writer();
|
||||
var output: std.ArrayListUnmanaged(u8) = .empty;
|
||||
errdefer output.deinit(alloc);
|
||||
var writer = output.writer(alloc);
|
||||
var index: usize = 0;
|
||||
while (wide_string[index] != 0) : (index += 1) {
|
||||
var buf: [4]u8 = undefined;
|
||||
const len = try std.unicode.utf8Encode(@intCast(wide_string[index]), &buf);
|
||||
try writer.writeAll(buf[0..len]);
|
||||
}
|
||||
return try output.toOwnedSlice();
|
||||
return try output.toOwnedSlice(alloc);
|
||||
}
|
||||
|
||||
fn to_wchar_alloc(alloc: std.mem.Allocator, string: []const u8) ![*c]hidapi.wchar_t {
|
||||
var list = std.ArrayList(hidapi.wchar_t).init(alloc);
|
||||
errdefer list.deinit();
|
||||
pub fn toWCharAlloc(alloc: std.mem.Allocator, string: []const u8) ![*c]c.wchar_t {
|
||||
var list: std.ArrayList(c.wchar_t) = .empty;
|
||||
errdefer list.deinit(alloc);
|
||||
var iter = (try std.unicode.Utf8View.init(string)).iterator();
|
||||
while (iter.nextCodepoint()) |codepoint| {
|
||||
try list.append(@intCast(codepoint));
|
||||
try list.append(alloc, @intCast(codepoint));
|
||||
}
|
||||
return try list.toOwnedSliceSentinel(0);
|
||||
return try list.toOwnedSliceSentinel(alloc, 0);
|
||||
}
|
||||
|
||||
fn to_wchar(string: []const u8, buffer: []hidapi.wchar_t) ![*c]hidapi.wchar_t {
|
||||
pub fn toWChar(string: []const u8, buffer: []c.wchar_t) ![*c]c.wchar_t {
|
||||
var iter = (try std.unicode.Utf8View.init(string)).iterator();
|
||||
var index: usize = 0;
|
||||
while (iter.nextCodepoint()) |codepoint| : (index += 1) {
|
||||
|
@ -380,3 +119,7 @@ fn to_wchar(string: []const u8, buffer: []hidapi.wchar_t) ![*c]hidapi.wchar_t {
|
|||
buffer[index] = 0;
|
||||
return buffer.ptr;
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue