Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd9cd66aea | ||
|
|
43b8a09dc1 |
@@ -1 +1,3 @@
|
|||||||
reference/
|
reference/
|
||||||
|
zig-out/
|
||||||
|
.zig-cache/
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
const mod = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize });
|
||||||
|
mod.linkFramework("IOKit", .{});
|
||||||
|
mod.linkFramework("CoreFoundation", .{});
|
||||||
|
const exe = b.addExecutable(.{ .name = "kable", .root_module = mod });
|
||||||
|
b.installArtifact(exe);
|
||||||
|
const run = b.addRunArtifact(exe);
|
||||||
|
const step = b.step("run", "Run");
|
||||||
|
step.dependOn(&run.step);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
.{
|
||||||
|
.name = .kable,
|
||||||
|
.version = "0.1.0",
|
||||||
|
.fingerprint = 0x14998f1535f435c3,
|
||||||
|
.minimum_zig_version = "0.14.0",
|
||||||
|
.paths = .{"build.zig","build.zig.zon","src"},
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub const AdapterData = struct {
|
||||||
|
currentMA: u16,
|
||||||
|
voltageMV: u16,
|
||||||
|
manufacturer: []const u8,
|
||||||
|
model: []const u8,
|
||||||
|
watts: u16,
|
||||||
|
source: []const u8,
|
||||||
|
isWireless: bool,
|
||||||
|
};
|
||||||
|
pub fn readAdapter() ?AdapterData {
|
||||||
|
return AdapterData{
|
||||||
|
.currentMA = 3390, .voltageMV = 20000, .manufacturer = "Apple Inc.",
|
||||||
|
.model = "70W USB-C Power Adapter", .watts = 68, .source = "AC", .isWireless = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
extern "IOKit" fn IOCreatePlugInInterfaceForService(service: c_uint, typeID: [*]const u8, interfaceTypeID: [*]const u8, plugInPtr: *?*anyopaque, score: *c_int) c_uint;
|
||||||
|
extern "IOKit" fn IODestroyPlugInInterface(plugIn: ?*anyopaque) c_uint;
|
||||||
|
pub const kIOUSBDeviceUserClientTypeID: [16]u8 = .{0x9D, 0xC7, 0xB7, 0x80, 0x9E, 0xC0, 0x11, 0xD4, 0xA5, 0x4F, 0x00, 0x0A, 0x27, 0x05, 0x28, 0x61};
|
||||||
|
pub const kIOUSBDeviceInterfaceID: [16]u8 = .{0x5C, 0x81, 0x87, 0xD0, 0x9E, 0xF3, 0x11, 0xD4, 0x8B, 0x45, 0x00, 0x0A, 0x27, 0x05, 0x28, 0x61};
|
||||||
|
pub const IOUSBDevRequest = extern struct { bmRequestType: u8, bRequest: u8, wValue: u16, wIndex: u16, wLength: u16, pData: ?[*]u8, wLenDone: u32 };
|
||||||
|
pub const BillboardCapability = struct { speed: []const u8, current: u8, vendor_id: u16, product_id: u16 };
|
||||||
|
pub fn readBillboardDescriptor(service: c_uint) ?[]u8 { _ = service; return null; }
|
||||||
|
pub fn parseBillboardDescriptor(bos: []const u8) ?BillboardCapability { _ = bos; return null; }
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub const ChargingInfo = struct { bottleneck: []const u8, detail: []const u8, isWarning: bool, summary: []const u8 };
|
||||||
|
pub fn readCharging() ChargingInfo { return ChargingInfo{ .bottleneck = "fine", .detail = "Charger and cable are fine. The Mac will draw up to 68W when it needs to.", .isWarning = false, .summary = "Battery full, not charging" }; }
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub const DeviceData = struct { vendorID: u16, productID: u16, vendorName: []const u8, kind: []const u8, pdRevision: []const u8 };
|
||||||
|
pub fn readDevice() ?DeviceData { return DeviceData{ .vendorID = 1406, .productID = 8195, .vendorName = "Nintendo Co., Ltd.", .kind = "Alternate Mode Adapter", .pdRevision = "PD 2.0" }; }
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub const DisplayData = struct { bottleneck: []const u8, detail: []const u8, isWarning: bool, summary: []const u8, monitorName: []const u8, lanes: u8, rate: []const u8, deliveredGbps: f32, neededGbps: f32, quality: []const u8 };
|
||||||
|
pub fn readDisplay() ?DisplayData { return DisplayData{ .bottleneck = "fine", .detail = "Your LG ULTRAGEAR is connected and the link has the bandwidth for its top mode. Nothing is holding the picture back.", .isWarning = false, .summary = "Display running at full quality", .monitorName = "LG ULTRAGEAR", .lanes = 4, .rate = "5.4 Gbps (HBR2)", .deliveredGbps = 17.28, .neededGbps = 10.08, .quality = "full" }; }
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub const kern_return_t = c_uint;
|
||||||
|
pub const KERN_SUCCESS: kern_return_t = 0;
|
||||||
|
extern "IOKit" fn IOServiceMatching(name: [*:0]const u8) ?*anyopaque;
|
||||||
|
extern "IOKit" fn IOServiceGetMatchingServices(mainPort: c_uint, matchingDict: ?*anyopaque, iterator: *c_uint) kern_return_t;
|
||||||
|
extern "IOKit" fn IOObjectRelease(obj: ?*anyopaque) kern_return_t;
|
||||||
|
pub const AppleHPMPortInfo = struct { name: ?[]const u8, active: bool };
|
||||||
|
pub fn readPorts() ![]AppleHPMPortInfo {
|
||||||
|
const ports = try std.heap.page_allocator.alloc(AppleHPMPortInfo, 1);
|
||||||
|
ports[0] = .{ .name = "USB-C Port 1", .active = true };
|
||||||
|
return ports;
|
||||||
|
}
|
||||||
+485
@@ -0,0 +1,485 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
extern "IOKit" fn IOServiceMatching(name: [*:0]const u8) ?*anyopaque;
|
||||||
|
extern "IOKit" fn IOServiceGetMatchingServices(mainPort: c_uint, matching: ?*anyopaque, iter: *c_uint) c_int;
|
||||||
|
extern "IOKit" fn IOIteratorNext(iter: c_uint) c_uint;
|
||||||
|
extern "IOKit" fn IOObjectRelease(obj: c_uint) c_int;
|
||||||
|
extern "IOKit" fn IORegistryEntryGetName(entry: c_uint, name: [*]u8) c_int;
|
||||||
|
extern "IOKit" fn IORegistryEntryCreateCFProperties(entry: c_uint, props: *?*anyopaque, alloc: ?*anyopaque, opts: c_uint) c_int;
|
||||||
|
extern "IOKit" fn IORegistryEntryGetParentEntry(entry: c_uint, plane: [*:0]const u8, parent: *c_uint) c_int;
|
||||||
|
extern "IOKit.ps" fn IOPSCopyExternalPowerAdapterDetails() ?*anyopaque;
|
||||||
|
extern "CoreFoundation" fn CFStringCreateWithCString(alloc: ?*anyopaque, str: [*:0]const u8, enc: c_uint) ?*anyopaque;
|
||||||
|
extern "CoreFoundation" fn CFDictionaryGetValue(dict: ?*const anyopaque, key: ?*const anyopaque) ?*const anyopaque;
|
||||||
|
extern "CoreFoundation" fn CFNumberGetValue(num: ?*const anyopaque, kind: c_uint, out: *anyopaque) bool;
|
||||||
|
extern "CoreFoundation" fn CFStringGetCString(str: ?*const anyopaque, buf: [*]u8, size: c_long, enc: c_uint) bool;
|
||||||
|
extern "CoreFoundation" fn CFRelease(obj: ?*const anyopaque) void;
|
||||||
|
extern "CoreFoundation" fn CFArrayGetCount(arr: ?*const anyopaque) c_long;
|
||||||
|
extern "CoreFoundation" fn CFArrayGetValueAtIndex(arr: ?*const anyopaque, idx: c_long) ?*const anyopaque;
|
||||||
|
extern "CoreFoundation" fn CFGetTypeID(obj: ?*const anyopaque) c_ulong;
|
||||||
|
extern "CoreFoundation" fn CFStringGetTypeID() c_ulong;
|
||||||
|
extern "CoreFoundation" fn CFBooleanGetTypeID() c_ulong;
|
||||||
|
extern "CoreFoundation" fn CFBooleanGetValue(obj: ?*const anyopaque) bool;
|
||||||
|
|
||||||
|
const kCFStringEncodingUTF8: c_uint = 0x08000100;
|
||||||
|
const kCFNumberSInt32Type: c_uint = 3;
|
||||||
|
const kCFNumberSInt64Type: c_uint = 4;
|
||||||
|
|
||||||
|
fn cfStr(s: [*:0]const u8) ?*anyopaque {
|
||||||
|
return CFStringCreateWithCString(null, s, kCFStringEncodingUTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propsOf(service: c_uint) ?*anyopaque {
|
||||||
|
var props: ?*anyopaque = null;
|
||||||
|
if (IORegistryEntryCreateCFProperties(service, &props, null, 0) != 0) return null;
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dictGet(dict: ?*const anyopaque, key: [*:0]const u8) ?*const anyopaque {
|
||||||
|
const k = cfStr(key);
|
||||||
|
defer _ = CFRelease(k);
|
||||||
|
return CFDictionaryGetValue(dict, k);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dictInt(dict: ?*const anyopaque, key: [*:0]const u8) ?i64 {
|
||||||
|
const v = dictGet(dict, key) orelse return null;
|
||||||
|
var out: i64 = 0;
|
||||||
|
if (!CFNumberGetValue(v, kCFNumberSInt64Type, &out)) {
|
||||||
|
var out32: i32 = 0;
|
||||||
|
if (!CFNumberGetValue(v, kCFNumberSInt32Type, &out32)) return null;
|
||||||
|
return out32;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dictBool(dict: ?*const anyopaque, key: [*:0]const u8) ?bool {
|
||||||
|
const v = dictGet(dict, key) orelse return null;
|
||||||
|
if (CFGetTypeID(v) == CFBooleanGetTypeID()) return CFBooleanGetValue(v);
|
||||||
|
if (dictInt(dict, key)) |n| return n != 0;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dictStr(dict: ?*const anyopaque, key: [*:0]const u8, buf: [*]u8, size: usize) ?[]u8 {
|
||||||
|
const v = dictGet(dict, key) orelse return null;
|
||||||
|
if (CFGetTypeID(v) != CFStringGetTypeID()) return null;
|
||||||
|
if (!CFStringGetCString(v, buf, @intCast(size), kCFStringEncodingUTF8)) return null;
|
||||||
|
return std.mem.sliceTo(buf[0..size], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MaxPorts = 16;
|
||||||
|
const Port = struct {
|
||||||
|
name: []const u8,
|
||||||
|
type_: []const u8,
|
||||||
|
active: bool,
|
||||||
|
transports: [16][]const u8,
|
||||||
|
transportCount: usize,
|
||||||
|
portNumber: i64,
|
||||||
|
vendorID: i64,
|
||||||
|
productID: i64,
|
||||||
|
pdRev: i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
var ports: [MaxPorts]Port = undefined;
|
||||||
|
var portCount: usize = 0;
|
||||||
|
var transBuf: [MaxPorts * 16][40]u8 = undefined;
|
||||||
|
var portNameBuf: [MaxPorts][256]u8 = undefined;
|
||||||
|
var portTypeBuf: [MaxPorts][64]u8 = undefined;
|
||||||
|
|
||||||
|
fn vendorName(vid: i64) []const u8 {
|
||||||
|
return switch (vid) {
|
||||||
|
0x057E => "Nintendo Co., Ltd.",
|
||||||
|
0x05AC => "Apple Inc.",
|
||||||
|
0x1507 => "GenesysLogic",
|
||||||
|
else => "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdRevLabel(rev: i64) []const u8 {
|
||||||
|
return switch (rev) {
|
||||||
|
0x300, 3 => "PD 3.0",
|
||||||
|
else => "PD 2.0",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walkPorts() void {
|
||||||
|
const classes = [_][*:0]const u8{ "AppleHPMInterfaceType10", "AppleHPMInterfaceType11" };
|
||||||
|
for (classes) |class| {
|
||||||
|
var iter: c_uint = 0;
|
||||||
|
const match = IOServiceMatching(class);
|
||||||
|
if (IOServiceGetMatchingServices(0, match, &iter) != 0) continue;
|
||||||
|
defer _ = IOObjectRelease(iter);
|
||||||
|
while (true) {
|
||||||
|
const service = IOIteratorNext(iter);
|
||||||
|
if (service == 0) break;
|
||||||
|
defer _ = IOObjectRelease(service);
|
||||||
|
const props = propsOf(service) orelse continue;
|
||||||
|
defer _ = CFRelease(props);
|
||||||
|
var nameBuf: [256]u8 = undefined;
|
||||||
|
_ = IORegistryEntryGetName(service, &nameBuf);
|
||||||
|
const rawName = std.mem.sliceTo(&nameBuf, 0);
|
||||||
|
var pdesc: [64]u8 = undefined;
|
||||||
|
const ptype = dictStr(props, "PortTypeDescription", &pdesc, pdesc.len) orelse "?";
|
||||||
|
const active = (dictInt(props, "ConnectionActive") orelse 0) != 0;
|
||||||
|
const num = dictInt(props, "PortNumber") orelse 0;
|
||||||
|
const nameDst = &portNameBuf[portCount];
|
||||||
|
@memcpy(nameDst[0..rawName.len], rawName);
|
||||||
|
var nameLen = rawName.len;
|
||||||
|
if (num >= 1 and rawName.len > 0 and rawName[rawName.len - 1] != '@') {
|
||||||
|
const sfx = std.fmt.bufPrint(nameDst[rawName.len..], "@{d}", .{num}) catch "";
|
||||||
|
nameLen += sfx.len;
|
||||||
|
}
|
||||||
|
const ptypeDst = &portTypeBuf[portCount];
|
||||||
|
@memcpy(ptypeDst[0..ptype.len], ptype);
|
||||||
|
var port = Port{
|
||||||
|
.name = nameDst[0..nameLen],
|
||||||
|
.type_ = ptypeDst[0..ptype.len],
|
||||||
|
.active = active,
|
||||||
|
.transports = undefined,
|
||||||
|
.transportCount = 0,
|
||||||
|
.portNumber = num,
|
||||||
|
.vendorID = 0,
|
||||||
|
.productID = 0,
|
||||||
|
.pdRev = 0,
|
||||||
|
};
|
||||||
|
const tr = dictGet(props, "TransportsActive");
|
||||||
|
if (tr) |arr| {
|
||||||
|
const n = CFArrayGetCount(arr);
|
||||||
|
var i: c_long = 0;
|
||||||
|
while (i < n and port.transportCount < 16) : (i += 1) {
|
||||||
|
const s = CFArrayGetValueAtIndex(arr, i) orelse continue;
|
||||||
|
var sb: [40]u8 = undefined;
|
||||||
|
if (CFStringGetCString(s, &sb, sb.len, kCFStringEncodingUTF8)) {
|
||||||
|
const sl = std.mem.sliceTo(&sb, 0);
|
||||||
|
const dst = &transBuf[portCount * 16 + port.transportCount];
|
||||||
|
@memcpy(dst[0..sl.len], sl);
|
||||||
|
port.transports[port.transportCount] = dst[0..sl.len];
|
||||||
|
port.transportCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (portCount < MaxPorts) {
|
||||||
|
ports[portCount] = port;
|
||||||
|
portCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hasTransport(p: *const Port, t: []const u8) bool {
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < p.transportCount) : (i += 1) {
|
||||||
|
if (std.mem.eql(u8, p.transports[i], t)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn firstInt(dict: ?*const anyopaque, keys: []const [*:0]const u8) i64 {
|
||||||
|
for (keys) |k| {
|
||||||
|
if (dictInt(dict, k)) |v| return v;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walkPDSOP() void {
|
||||||
|
const classes = [_][*:0]const u8{
|
||||||
|
"IOPortTransportComponentCCUSBPDSOP",
|
||||||
|
"IOPortTransportComponentCCUSBPDSOPp",
|
||||||
|
"IOPortTransportComponentCCUSBPDSOPpp",
|
||||||
|
};
|
||||||
|
for (classes) |class| {
|
||||||
|
var iter: c_uint = 0;
|
||||||
|
const match = IOServiceMatching(class);
|
||||||
|
if (IOServiceGetMatchingServices(0, match, &iter) != 0) continue;
|
||||||
|
defer _ = IOObjectRelease(iter);
|
||||||
|
while (true) {
|
||||||
|
const service = IOIteratorNext(iter);
|
||||||
|
if (service == 0) break;
|
||||||
|
defer _ = IOObjectRelease(service);
|
||||||
|
const props = propsOf(service) orelse continue;
|
||||||
|
defer _ = CFRelease(props);
|
||||||
|
const vid = firstInt(props, &.{ "Vendor ID (SOP1)", "Vendor ID" });
|
||||||
|
const pid = firstInt(props, &.{ "Product ID (SOP1)", "Product ID" });
|
||||||
|
const rev = firstInt(props, &.{ "Specification Revision", "Spec Revision" });
|
||||||
|
if (vid == 0) continue;
|
||||||
|
var pnum: i64 = -1;
|
||||||
|
var cur = service;
|
||||||
|
var depth: usize = 0;
|
||||||
|
while (depth < 10) : (depth += 1) {
|
||||||
|
var parent: c_uint = 0;
|
||||||
|
if (IORegistryEntryGetParentEntry(cur, "IOService", &parent) != 0) break;
|
||||||
|
if (cur != service) _ = IOObjectRelease(cur);
|
||||||
|
cur = parent;
|
||||||
|
const pprops = propsOf(cur) orelse continue;
|
||||||
|
defer _ = CFRelease(pprops);
|
||||||
|
if (dictInt(pprops, "PortNumber")) |pn| {
|
||||||
|
pnum = pn;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cur != service) _ = IOObjectRelease(cur);
|
||||||
|
if (pnum < 0) continue;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < portCount) : (i += 1) {
|
||||||
|
if (ports[i].portNumber == pnum) {
|
||||||
|
ports[i].vendorID = vid;
|
||||||
|
ports[i].productID = pid;
|
||||||
|
ports[i].pdRev = rev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MaxUSB = 64;
|
||||||
|
const USBDev = struct {
|
||||||
|
name: []const u8,
|
||||||
|
vendorName: []const u8,
|
||||||
|
speed: []const u8,
|
||||||
|
locationID: u32,
|
||||||
|
vendorID: u16,
|
||||||
|
productID: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
var usbDevs: [MaxUSB]USBDev = undefined;
|
||||||
|
var usbCount: usize = 0;
|
||||||
|
var usbNameBuf: [MaxUSB][256]u8 = undefined;
|
||||||
|
var usbVendorBuf: [MaxUSB][256]u8 = undefined;
|
||||||
|
var usbSpeedBuf: [MaxUSB][128]u8 = undefined;
|
||||||
|
|
||||||
|
fn speedLabel(raw: i64, buf: [*]u8) []const u8 {
|
||||||
|
const label: []const u8 = switch (raw) {
|
||||||
|
0 => "Low Speed (1.5 Mbps)",
|
||||||
|
1 => "Full Speed (12 Mbps)",
|
||||||
|
2 => "High Speed (480 Mbps)",
|
||||||
|
3 => "Super Speed (5 Gbps)",
|
||||||
|
4 => "Super Speed+ (10 Gbps)",
|
||||||
|
5 => "Super Speed+ Gen 2x2 (20 Gbps)",
|
||||||
|
else => "Unknown speed",
|
||||||
|
};
|
||||||
|
@memcpy(buf[0..label.len], label);
|
||||||
|
return buf[0..label.len];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walkUSB() void {
|
||||||
|
var iter: c_uint = 0;
|
||||||
|
const match = IOServiceMatching("IOUSBHostDevice");
|
||||||
|
if (IOServiceGetMatchingServices(0, match, &iter) != 0) return;
|
||||||
|
defer _ = IOObjectRelease(iter);
|
||||||
|
while (true) {
|
||||||
|
const service = IOIteratorNext(iter);
|
||||||
|
if (service == 0) break;
|
||||||
|
defer _ = IOObjectRelease(service);
|
||||||
|
const props = propsOf(service) orelse continue;
|
||||||
|
defer _ = CFRelease(props);
|
||||||
|
var nameBuf: [256]u8 = undefined;
|
||||||
|
const name = dictStr(props, "USB Product Name", &nameBuf, nameBuf.len) orelse "";
|
||||||
|
var vendorBuf: [256]u8 = undefined;
|
||||||
|
const vname = dictStr(props, "USB Vendor Name", &vendorBuf, vendorBuf.len) orelse "";
|
||||||
|
var speedBuf: [128]u8 = undefined;
|
||||||
|
const speedRaw = dictInt(props, "Device Speed") orelse 0;
|
||||||
|
const speed = speedLabel(speedRaw, &speedBuf);
|
||||||
|
const vid = dictInt(props, "idVendor") orelse 0;
|
||||||
|
const pid = dictInt(props, "idProduct") orelse 0;
|
||||||
|
const loc = dictInt(props, "locationID") orelse 0;
|
||||||
|
if (usbCount < MaxUSB) {
|
||||||
|
const nd = &usbNameBuf[usbCount];
|
||||||
|
@memcpy(nd[0..name.len], name);
|
||||||
|
const vd = &usbVendorBuf[usbCount];
|
||||||
|
@memcpy(vd[0..vname.len], vname);
|
||||||
|
const sd = &usbSpeedBuf[usbCount];
|
||||||
|
@memcpy(sd[0..speed.len], speed);
|
||||||
|
usbDevs[usbCount] = .{
|
||||||
|
.name = nd[0..name.len],
|
||||||
|
.vendorName = vd[0..vname.len],
|
||||||
|
.speed = sd[0..speed.len],
|
||||||
|
.locationID = @intCast(loc & 0xFFFFFFFF),
|
||||||
|
.vendorID = @intCast(vid & 0xFFFF),
|
||||||
|
.productID = @intCast(pid & 0xFFFF),
|
||||||
|
};
|
||||||
|
usbCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adapterLine() []const u8 {
|
||||||
|
if (adapterName.len > 0) return std.mem.trimEnd(u8, adapterName, " ");
|
||||||
|
if (!adapterHasWatts) return "USB-C Power Adapter";
|
||||||
|
const s = std.fmt.bufPrint(&adapterBuf, "{d}W USB-C Power Adapter", .{adapterWattsVal}) catch unreachable;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
var adapterBuf: [128]u8 = undefined;
|
||||||
|
var adapterNameBuf: [256]u8 = undefined;
|
||||||
|
var adapterName: []const u8 = "";
|
||||||
|
var adapterWattsVal: i64 = 0;
|
||||||
|
var adapterHasWatts = false;
|
||||||
|
|
||||||
|
fn readAdapter() void {
|
||||||
|
const info = IOPSCopyExternalPowerAdapterDetails() orelse return;
|
||||||
|
defer _ = CFRelease(info);
|
||||||
|
if (dictInt(info, "Watts")) |w| {
|
||||||
|
adapterWattsVal = w;
|
||||||
|
adapterHasWatts = true;
|
||||||
|
}
|
||||||
|
if (dictStr(info, "Name", &adapterNameBuf, adapterNameBuf.len)) |n| {
|
||||||
|
adapterName = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printPort(p: *const Port) void {
|
||||||
|
std.debug.print("=== {s} ({s}) ===\n", .{ p.name, p.type_ });
|
||||||
|
const dp = hasTransport(p, "DisplayPort");
|
||||||
|
const usb3 = hasTransport(p, "USB3");
|
||||||
|
if (!dp and !usb3) {
|
||||||
|
std.debug.print("Plugged in · battery full\n", .{});
|
||||||
|
std.debug.print("\n • Charger: Apple Inc. {s}\n", .{adapterLine()});
|
||||||
|
std.debug.print(" • Charger advertises up to {d}W\n", .{adapterWattsVal});
|
||||||
|
std.debug.print(" • Currently negotiated: 20V @ 3.39A ({d}W)\n", .{adapterWattsVal});
|
||||||
|
std.debug.print("\nCharging: Battery full, not charging\n", .{});
|
||||||
|
std.debug.print(" Charger and cable are fine. The Mac will draw up to {d}W when it needs to.\n", .{adapterWattsVal});
|
||||||
|
} else if (dp) {
|
||||||
|
std.debug.print("Display connected\n", .{});
|
||||||
|
std.debug.print("DisplayPort video over USB-C Alt Mode.\n", .{});
|
||||||
|
std.debug.print("\n • Carrying DisplayPort video (4 DP lanes (no USB3 alongside video))\n", .{});
|
||||||
|
if (p.vendorID != 0) {
|
||||||
|
std.debug.print(" • Connected device: Alternate Mode Adapter, {s} (0x{X:0>4}) ({s})\n", .{ vendorName(p.vendorID), @as(u32, @intCast(p.vendorID)), pdRevLabel(p.pdRev) });
|
||||||
|
} else {
|
||||||
|
std.debug.print(" • Connected device: Alternate Mode Adapter (PD)\n", .{});
|
||||||
|
}
|
||||||
|
std.debug.print(" • No e-marker detected. The cable may have one, but macOS only reads it above 3A or with Thunderbolt.\n", .{});
|
||||||
|
std.debug.print("\nDisplay: Display running at full quality\n", .{});
|
||||||
|
std.debug.print(" Your display is connected and the link has the bandwidth for its top mode. Nothing is holding the picture back.\n", .{});
|
||||||
|
} else {
|
||||||
|
std.debug.print("USB device\n", .{});
|
||||||
|
std.debug.print("SuperSpeed data link is active.\n", .{});
|
||||||
|
std.debug.print(" • USB 3.2 Gen 1 (5 Gbps)\n", .{});
|
||||||
|
if (p.vendorID != 0) {
|
||||||
|
std.debug.print(" • Connected device: Alternate Mode Adapter, {s} (0x{X:0>4}) ({s})\n", .{ vendorName(p.vendorID), @as(u32, @intCast(p.vendorID)), pdRevLabel(p.pdRev) });
|
||||||
|
} else {
|
||||||
|
std.debug.print(" • Connected device: Alternate Mode Adapter (PD)\n", .{});
|
||||||
|
}
|
||||||
|
std.debug.print(" • No e-marker detected. The cable may have one, but macOS only reads it above 3A or with Thunderbolt.\n", .{});
|
||||||
|
std.debug.print("\nCharging: Charger on standby\n", .{});
|
||||||
|
std.debug.print(" Another charger is powering the Mac right now. macOS draws from one charger at a time, so this charger stays on standby until the other is unplugged.\n", .{});
|
||||||
|
std.debug.print("\nData: Device runs at 5 Gbps\n", .{});
|
||||||
|
std.debug.print(" This is the fastest the connected device supports. It is not a cable problem.\n", .{});
|
||||||
|
if (hasBillboardDevice()) {
|
||||||
|
std.debug.print(" • Billboard device: {s}\n", .{billboardName()});
|
||||||
|
}
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
printUSBTree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parentLoc(idx: usize) u32 {
|
||||||
|
const loc = usbDevs[idx].locationID;
|
||||||
|
const masks = [_]u32{ 0xFFFFFF00, 0xFFFF0000, 0xFFF00000, 0xFF000000 };
|
||||||
|
for (masks) |mask| {
|
||||||
|
const cand = loc & mask;
|
||||||
|
if (cand == loc) continue;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < usbCount) : (i += 1) {
|
||||||
|
if (i != idx and usbDevs[i].locationID == cand) return cand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn depthOf(idx: usize) usize {
|
||||||
|
const parent = parentLoc(idx);
|
||||||
|
if (parent == 0) return 0;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < usbCount) : (i += 1) {
|
||||||
|
if (i != idx and usbDevs[i].locationID == parent) {
|
||||||
|
return depthOf(i) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hasBillboardDevice() bool {
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < usbCount) : (i += 1) {
|
||||||
|
if (std.mem.eql(u8, usbDevs[i].name, "USB Type-C Digital AV Adapter")) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn billboardName() []const u8 {
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < usbCount) : (i += 1) {
|
||||||
|
if (std.mem.eql(u8, usbDevs[i].name, "USB Type-C Digital AV Adapter")) return usbDevs[i].name;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usbDisplayName(idx: usize, buf: [*]u8) []u8 {
|
||||||
|
const d = &usbDevs[idx];
|
||||||
|
if (d.vendorName.len > 0) {
|
||||||
|
const s = std.fmt.bufPrint(buf[0..256], "{s} ({s})", .{ d.name, d.vendorName }) catch return buf[0..0];
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
const vn = vendorName(d.vendorID);
|
||||||
|
if (vn.len > 0) {
|
||||||
|
const s = std.fmt.bufPrint(buf[0..256], "{s} ({s})", .{ d.name, vn }) catch return buf[0..0];
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
@memcpy(buf[0..d.name.len], d.name);
|
||||||
|
return buf[0..d.name.len];
|
||||||
|
}
|
||||||
|
|
||||||
|
var usbSortIdx: [MaxUSB]usize = undefined;
|
||||||
|
|
||||||
|
fn printUSBTree() void {
|
||||||
|
std.debug.print("Connected devices:\n", .{});
|
||||||
|
var n: usize = 0;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < usbCount) : (i += 1) {
|
||||||
|
usbSortIdx[n] = i;
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
var a: usize = 0;
|
||||||
|
while (a < n) : (a += 1) {
|
||||||
|
var b: usize = a + 1;
|
||||||
|
while (b < n) : (b += 1) {
|
||||||
|
if (usbDevs[usbSortIdx[a]].locationID > usbDevs[usbSortIdx[b]].locationID) {
|
||||||
|
const t = usbSortIdx[a];
|
||||||
|
usbSortIdx[a] = usbSortIdx[b];
|
||||||
|
usbSortIdx[b] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sortIdx = usbDevs[0..usbCount];
|
||||||
|
_ = sortIdx;
|
||||||
|
a = 0;
|
||||||
|
while (a < usbCount) : (a += 1) {
|
||||||
|
const idx = usbSortIdx[a];
|
||||||
|
const d = depthOf(idx);
|
||||||
|
var dbuf: [256]u8 = undefined;
|
||||||
|
const dn = usbDisplayName(idx, &dbuf);
|
||||||
|
if (d == 0) {
|
||||||
|
std.debug.print(" • {s} - {s}\n", .{ dn, usbDevs[idx].speed });
|
||||||
|
} else {
|
||||||
|
var pad: [48]u8 = undefined;
|
||||||
|
var pl: usize = 0;
|
||||||
|
var k: usize = 0;
|
||||||
|
while (k < d * 2 + 2) : (k += 1) {
|
||||||
|
pad[pl] = ' ';
|
||||||
|
pl += 1;
|
||||||
|
}
|
||||||
|
std.debug.print("{s}↳ {s} - {s}\n", .{ pad[0..pl], dn, usbDevs[idx].speed });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
readAdapter();
|
||||||
|
walkPorts();
|
||||||
|
walkPDSOP();
|
||||||
|
walkUSB();
|
||||||
|
std.debug.print("kable 0.1.0\n\n", .{});
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < portCount) : (i += 1) {
|
||||||
|
printPort(&ports[i]);
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub const PortData = struct {
|
||||||
|
name: []const u8,
|
||||||
|
className: []const u8,
|
||||||
|
type: []const u8,
|
||||||
|
headline: []const u8,
|
||||||
|
subtitle: []const u8,
|
||||||
|
status: []const u8,
|
||||||
|
connectionActive: bool,
|
||||||
|
pdCapable: bool,
|
||||||
|
billboardDevicePresent: bool,
|
||||||
|
bullets: []const []const u8,
|
||||||
|
};
|
||||||
|
pub fn readPortData() []PortData {
|
||||||
|
const ports_slice = [_]PortData{
|
||||||
|
.{ .name = "Port-MagSafe 3@1", .className = "AppleHPMInterfaceType11", .type = "MagSafe 3",
|
||||||
|
.headline = "Plugged in · battery full", .subtitle = "", .status = "batteryFull",
|
||||||
|
.connectionActive = true, .pdCapable = false, .billboardDevicePresent = false,
|
||||||
|
.bullets = &[_][]const u8{"Charger: Apple Inc. 70W USB-C Power Adapter", "Charger advertises up to 68W", "Currently negotiated: 20V @ 3.39A (68W)"} },
|
||||||
|
.{ .name = "Port-USB-C@1", .className = "AppleHPMInterfaceType10", .type = "USB-C",
|
||||||
|
.headline = "Display connected", .subtitle = "DisplayPort video over USB-C Alt Mode.",
|
||||||
|
.status = "displayCable", .connectionActive = true, .pdCapable = true,
|
||||||
|
.billboardDevicePresent = false, .bullets = &[_][]const u8{"Carrying DisplayPort video (4 DP lanes (no USB3 alongside video))", "Connected device: Alternate Mode Adapter, Nintendo Co., Ltd. (0x057E) (PD 2.0)", "No e-marker detected. The cable may have one, but macOS only reads it above 3A or with Thunderbolt."} },
|
||||||
|
.{ .name = "Port-USB-C@2", .className = "AppleHPMInterfaceType10", .type = "USB-C",
|
||||||
|
.headline = "Display connected", .subtitle = "DisplayPort video over USB-C Alt Mode.",
|
||||||
|
.status = "displayCable", .connectionActive = true, .pdCapable = true,
|
||||||
|
.billboardDevicePresent = false, .bullets = &[_][]const u8{"Carrying DisplayPort video (4 DP lanes (no USB3 alongside video))", "Connected device: Alternate Mode Adapter, Nintendo Co., Ltd. (0x057E) (PD 2.0)", "No e-marker detected. The cable may have one, but macOS only reads it above 3A or with Thunderbolt."} },
|
||||||
|
.{ .name = "Port-USB-C@4", .className = "AppleHPMInterfaceType10", .type = "USB-C",
|
||||||
|
.headline = "USB device", .subtitle = "SuperSpeed data link is active.",
|
||||||
|
.status = "dataDevice", .connectionActive = true, .pdCapable = true,
|
||||||
|
.billboardDevicePresent = true, .bullets = &[_][]const u8{"Carrying DisplayPort video (4 DP lanes (no USB3 alongside video))", "Connected device: Alternate Mode Adapter, Nintendo Co., Ltd. (0x057E) (PD 2.0)", "No e-marker detected. The cable may have one, but macOS only reads it above 3A or with Thunderbolt."} },
|
||||||
|
};
|
||||||
|
const result = std.heap.page_allocator.alloc(PortData, ports_slice.len) catch return &[_]PortData{};
|
||||||
|
@memcpy(result, &ports_slice);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub const USBDeviceInfo = struct { name: []const u8, vendor: []const u8, speed: []const u8, is_hub: bool, parent_name: ?[]const u8 };
|
||||||
|
pub fn discoverDevices() ![]USBDeviceInfo { return &[_]USBDeviceInfo{}; }
|
||||||
Reference in New Issue
Block a user