const USB_IDS_PATH = @import("build_options").USB_IDS_PATH; const list = @import("list"); const device = @import("device"); const enumerate = @import("enumerate"); const monitor = @import("monitor"); const std = @import("std"); const c = @import("ffi").c; const mem = std.mem; const fmt = std.fmt; const Io = std.Io; const alloc = std.heap.c_allocator; const io = Io.Threaded.global_single_threaded.io(); comptime { _ = device; _ = enumerate; _ = monitor; } pub const Udev = struct { ref_count: c_int = 1, const Self = @This(); fn asC(self: *Self) *c.struct_udev { return @ptrCast(self); } fn fromC(ptr: *c.struct_udev) *Self { return @ptrCast(@alignCast(ptr)); } }; export fn udev_new() callconv(.c) ?*c.struct_udev { const udev = alloc.create(Udev) catch return null; udev.* = .{}; return udev.asC(); } export fn udev_ref(udev: ?*c.struct_udev) callconv(.c) ?*c.struct_udev { const ptr = udev orelse return null; Udev.fromC(ptr).ref_count += 1; return ptr; } export fn udev_unref(udev: ?*c.struct_udev) callconv(.c) ?*c.struct_udev { const ptr = udev orelse return null; const self = Udev.fromC(ptr); self.ref_count -= 1; if (self.ref_count > 0) { return self.asC(); } alloc.destroy(self); return null; } const LogFn = @typeInfo(@TypeOf(c.udev_set_log_fn)).@"fn".params[1].type.?; export fn udev_set_log_fn( udev: ?*c.struct_udev, log_fn: LogFn, ) callconv(.c) void { _ = udev; _ = log_fn; } export fn udev_set_log_priority(udev: ?*c.struct_udev, priority: c_int) callconv(.c) void { _ = udev; _ = priority; } export fn udev_get_log_priority(udev: ?*c.struct_udev) callconv(.c) c_int { _ = udev; return 0; } const Hwdb = struct { head: list.ListEntry = .{}, refcount: c_int, const Self = @This(); fn asC(self: *Self) *c.struct_udev_hwdb { return @ptrCast(self); } fn fromC(ptr: *c.struct_udev_hwdb) *Self { return @ptrCast(@alignCast(ptr)); } }; fn copyUsbIdsName(buf: []u8, line: []const u8, search_start: usize) bool { const space_offset = mem.indexOfScalar(u8, line[search_start..], ' ') orelse return false; const name = mem.trimStart(u8, line[search_start + space_offset ..], " "); if (name.len == 0) return false; const copy_len = @min(name.len, buf.len - 1); @memcpy(buf[0..copy_len], name[0..copy_len]); buf[copy_len] = 0; return true; } pub fn usb_ids_lookup_vendor(vendorid: u16, buf: []u8) bool { if (buf.len == 0) return false; buf[0] = 0; const file = Io.Dir.cwd().openFile(io, USB_IDS_PATH, .{}) catch return false; defer file.close(io); var read_buffer: [4096]u8 = undefined; var file_reader = file.reader(io, &read_buffer); const reader = &file_reader.interface; while (reader.takeDelimiter('\n') catch return false) |line| { const clean_line = mem.trimEnd(u8, line, "\r"); if (clean_line.len < 4 or clean_line[0] == '#' or clean_line[0] == '\t') { continue; } const id = fmt.parseInt(u16, clean_line[0..4], 16) catch continue; if (id != vendorid) continue; return copyUsbIdsName(buf, clean_line, 0); } return false; } pub fn usb_ids_lookup_product(vendorid: u16, productid: u16, buf: []u8) bool { if (buf.len == 0) return false; buf[0] = 0; const file = Io.Dir.cwd().openFile(io, USB_IDS_PATH, .{}) catch return false; defer file.close(io); var read_buffer: [4096]u8 = undefined; var file_reader = file.reader(io, &read_buffer); const reader = &file_reader.interface; var in_vendor = false; while (reader.takeDelimiter('\n') catch return false) |line| { const clean_line = mem.trimEnd(u8, line, "\r"); if (clean_line.len == 0 or clean_line[0] == '#') continue; if (in_vendor) { if (clean_line[0] != '\t') break; if (clean_line.len < 5 or clean_line[1] == '\t') continue; const id = fmt.parseInt(u16, clean_line[1..5], 16) catch continue; if (id != productid) continue; return copyUsbIdsName(buf, clean_line, 1); } if (clean_line[0] == '\t' or clean_line.len < 4) continue; const id = fmt.parseInt(u16, clean_line[0..4], 16) catch continue; if (id == vendorid) in_vendor = true; } return false; } export fn udev_hwdb_new(udev: ?*c.struct_udev) callconv(.c) ?*c.struct_udev_hwdb { _ = udev; const hwdb = alloc.create(Hwdb) catch return null; hwdb.* = .{ .refcount = 1 }; return hwdb.asC(); } export fn udev_hwdb_ref(hwdb: ?*c.struct_udev_hwdb) callconv(.c) ?*c.struct_udev_hwdb { const ptr = hwdb orelse return null; Hwdb.fromC(ptr).refcount += 1; return hwdb; } export fn udev_hwdb_unref(hwdb: ?*c.struct_udev_hwdb) callconv(.c) ?*c.struct_udev_hwdb { const ptr = hwdb orelse return null; const self = Hwdb.fromC(ptr); self.refcount -= 1; if (self.refcount > 0) { return null; } list.udev_list_entry_free_all(self.head.asC()); alloc.destroy(self); return null; } export fn udev_hwdb_get_properties_list_entry(hwdb: ?*c.struct_udev_hwdb, modalias: [*c]const u8, flags: c_uint) callconv(.c) ?*c.udev_list_entry { _ = flags; const hwdb_ptr = hwdb orelse return null; const modalias_ptr = modalias orelse return null; const self = Hwdb.fromC(hwdb_ptr); list.udev_list_entry_free_all(self.head.asC()); self.head.next = null; const text = mem.span(modalias_ptr); const prefix = "usb:v"; var name: [64]u8 = undefined; if (mem.startsWith(u8, text, prefix) and text.len >= prefix.len + 4) { const vendor_start = prefix.len; const vendor_end = vendor_start + 4; if (fmt.parseInt( u16, text[vendor_start..vendor_end], 16, )) |vendor_id| { const product_start = vendor_end + 1; const product_end = product_start + 4; if (text.len >= product_end and text[vendor_end] == 'p') { if (fmt.parseInt( u16, text[product_start..product_end], 16, )) |product_id| { if (usb_ids_lookup_product(vendor_id, product_id, &name)) { _ = list.udev_list_entry_add(self.head.asC(), "ID_MODEL_FROM_DATABASE", name[0..].ptr, 0); } } else |_| {} } else { if (usb_ids_lookup_vendor(vendor_id, &name)) { _ = list.udev_list_entry_add(self.head.asC(), "ID_VENDOR_FROM_DATABASE", name[0..].ptr, 0); } } } else |_| {} } if (self.head.next) |next| { return next.asC(); } else { return null; } }