はじめに
はじめに
不定期で開催されるクラフェスの電子工作(分解教室)用の材料集めメモです。
2025.11.24
HARDOFF
八王子大和田店でCanon LS-120TK IIIを入手しましたが、既に【サ終】(サポート終了)となっているデバイスです。
Canon LS-120TK III
WindowsとLinux (Raspberry Pi)では標準ドライバーで正常に認識され、テンキー(キーボード)として使用できました。
しかし、macOSではUSBデバイスとして認識はされましたが、テンキーとして使用できませんでした。
$ system_profiler SPUSBDataType
USB:
USB 3.1 Bus:
Host Controller Driver: AppleT8103USBXHCI
Composite Device:
Product ID: 0x6523
Vendor ID: 0x1267
Version: 1.10
Speed: Up to 1.5 Mb/s
Location ID: 0x01100000 / 1
Current Available (mA): 500
Current Required (mA): 100
Extra Operating Current (mA): 0
正確に言うと、電卓に「PC/計算」(電卓とPC入力モードを切り替える)ボタンが付いているのですが、押しても反応しない(PC入力モードに切り替えられない)状態でした。
調査
Canon LS-120TK IIIをmacOSで使用している事例を調査したところ、古いブログページが見つかりました。
DentakuTenkeyというソフトウェアをインストールすれば動作するようですが、古いホームページで既にリンク先(GeoCities)が【サ終】(2019年3月末にサービス終了)になっていました。
インターネットアーカイブサイトで入手
インターネットアーカイブサイトの「Wayback Machine - Internet Archive」でソフトウェアの入手に成功しました。
MacOS X 10.6までしか対応していないため、当然、Macbook Air M1 (macOS Sequoia 15.7.3)にはインストールすらできませんでしたが、ソースコードが付属していたため、Xcodeでビルドすることにしました。
Xcodeでのビルドに失敗
IOKit/hid/IOHIDEvent.h file not foundというエラーが発生しました。
ChatGPTに聞いたところ、Appleが仕様変更で非公開(Private API)扱いにし、Xcode 15 / macOS 14(Sonoma) で遮断したとのことでした。
Xcodeでのビルドに成功するもkextloadに失敗
MacKernelSDKをダウンロードして、不足しているヘッダーファイルを取り込んだところビルドは成功しました。
しかし、実際に/sbin/kextloadコマンドを実行したところ、エラー「failed to load (libkern/kext) not found」が発生しました。
Karabiner-Elementsを試す
古いブログページの記載がヒントになりました。
「KeyRemap4MacBookをベースにタイトルのソフトを作ってみました。」
「KeyRemap4MacBook」は現在「Karabiner」になっています。
ということで、Karabiner-Elementsをインストールして試してみましたが、デバイスをPC入力モードに変更できないと、そもそもmacOS側でキー入力を検知できませんでした。
解析
ソフトウェアを動かすことを諦めて、正常に動作しているデバイスの通信を解析して、直接デバイスを制御する方針に変更しました。
デバイスが正常に動作しているLinux (Raspberry Pi)でUSB (HID)の通信をキャプチャーします。
USBトラフィックをモニタリングするために、usbmonモジュールをロードします。
$ sudo modprobe usbmon
WiresharkでUSBトラフィックをキャプチャーします。
$ sudo apt install wireshark
$ sudo wireshark
無事にデバイスの初期化ログを取得できました。
08:45:35.223123 USB CONTROL SUBMIT to 1:5:0
08:45:35.224380 USB CONTROL COMPLETE from 1:5:0
0x0000: 1201 1001 0000 0008 6712 2365 1001 0000 ........g.#e....
0x0010: 0001 ..
08:45:35.224432 USB CONTROL SUBMIT to 1:5:0
08:45:35.225502 USB CONTROL COMPLETE from 1:5:0
0x0000: 0902 2200 0101 0080 32 ..".....2
08:45:35.225534 USB CONTROL SUBMIT to 1:5:0
08:45:35.227323 USB CONTROL COMPLETE from 1:5:0
0x0000: 0902 2200 0101 0080 3209 0400 0001 0301 ..".....2.......
0x0010: 0100 0921 1001 0001 2241 0007 0581 0308 ...!...."A......
0x0020: 000a ..
08:45:35.229038 USB CONTROL SUBMIT to 1:5:0
08:45:35.229762 USB CONTROL COMPLETE from 1:5:0
08:45:35.230455 USB CONTROL SUBMIT to 1:5:0
08:45:35.232276 USB CONTROL COMPLETE from 1:5:0
08:45:35.232329 USB CONTROL SUBMIT to 1:5:0
08:45:35.235151 USB CONTROL COMPLETE from 1:5:0
0x0000: 0501 0906 a101 0507 19e0 29e7 1500 2501 ..........)...%.
0x0010: 9508 7501 8102 9508 7501 8101 0508 1901 ..u.....u.......
0x0020: 2905 9505 7501 9102 9501 7503 9101 0507 )...u.....u.....
0x0030: 1900 2aff 0015 0026 ff00 9506 7508 8100 ..*....&....u...
0x0040: c0 .
08:45:35.238150 USB CONTROL SUBMIT to 1:5:0
0x0000: 00
PC入力モードに切り替える代替え手段
結果的に古いブログページの記載がヒントになりました。
「デバイス情報からは一般的なキーパッドの様な感じでしたので、LED辺りを操作してやれば動くのでは?と試してみた所、接続時にNumLock LEDをONするコードを送ってやれば良い様です。」
setledsmac
HIDデバイスへNumLock LEDをONする方法をChatGPTへ聞いたところ、setledsmacツールをインストールする方法です。
$ git clone https://github.com/damieng/setledsmac.git
$ cd setledsmac/Source/SetLEDs
$ make
$ ./setleds +num
実行したところ、残念ながらキーボードとして認識していない(HID(キーボード)としてデバイス名が見えていない)ため失敗しました。
% ./setleds +num
SetLEDs version 0.4 - https://github.com/damieng/setledsmac
"Apple Internal Keyboard / Trackpad" +num
コードを作成
次にHIDデバイスへNumLock LEDをONするソースコードをChatGPTに作成してもらいました。
ソースコード
// hid_set_numlock.c
// Build: clang hid_set_numlock.c -o hid_set_numlock -framework IOKit -framework CoreFoundation
// Usage: sudo ./hid_set_numlock 0x1267 0x6523 0x01100000
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/hid/IOHIDManager.h>
#include <stdio.h>
#include <stdlib.h>
static int hex_or_dec(const char* s) {
// accepts "0x...." or decimal
return (int)strtol(s, NULL, 0);
}
static void print_cfstring(CFStringRef s) {
if (!s) { printf("(null)"); return; }
char buf[512];
if (CFStringGetCString(s, buf, sizeof(buf), kCFStringEncodingUTF8)) printf("%s", buf);
else printf("(cfstring)");
}
int main(int argc, char** argv) {
if (argc < 4) {
fprintf(stderr, "Usage: %s <vid> <pid> <location_id>\n", argv[0]);
fprintf(stderr, "Example: %s 0x1267 0x6523 0x01100000\n", argv[0]);
return 1;
}
int vid = hex_or_dec(argv[1]);
int pid = hex_or_dec(argv[2]);
int loc = hex_or_dec(argv[3]);
IOHIDManagerRef mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
// Matching dict: vendor/product (Apple docs: kIOHIDVendorIDKey, kIOHIDProductIDKey) :contentReference[oaicite:5]{index=5}
CFMutableDictionaryRef match = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFNumberRef nVid = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vid);
CFNumberRef nPid = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pid);
CFDictionarySetValue(match, CFSTR(kIOHIDVendorIDKey), nVid);
CFDictionarySetValue(match, CFSTR(kIOHIDProductIDKey), nPid);
CFRelease(nVid);
CFRelease(nPid);
IOHIDManagerSetDeviceMatching(mgr, match);
CFRelease(match);
IOReturn r = IOHIDManagerOpen(mgr, kIOHIDOptionsTypeNone);
if (r != kIOReturnSuccess) {
fprintf(stderr, "IOHIDManagerOpen failed: 0x%08x\n", r);
return 2;
}
CFSetRef devs = IOHIDManagerCopyDevices(mgr);
if (!devs) {
fprintf(stderr, "No devices matched VID/PID\n");
return 3;
}
CFIndex count = CFSetGetCount(devs);
IOHIDDeviceRef* list = (IOHIDDeviceRef*)calloc((size_t)count, sizeof(IOHIDDeviceRef));
CFSetGetValues(devs, (const void**)list);
IOHIDDeviceRef target = NULL;
for (CFIndex i = 0; i < count; i++) {
IOHIDDeviceRef d = list[i];
// Location IDで絞る (kIOHIDLocationIDKey) :contentReference[oaicite:6]{index=6}
CFTypeRef locRef = IOHIDDeviceGetProperty(d, CFSTR(kIOHIDLocationIDKey));
int dloc = -1;
if (locRef && CFGetTypeID(locRef) == CFNumberGetTypeID()) {
CFNumberGetValue((CFNumberRef)locRef, kCFNumberIntType, &dloc);
}
CFStringRef prod = (CFStringRef)IOHIDDeviceGetProperty(d, CFSTR(kIOHIDProductKey));
printf("Matched device: location=0x%08x product=", dloc);
print_cfstring(prod);
printf("\n");
if (dloc == loc) {
target = d;
}
}
if (!target) {
fprintf(stderr, "Device matched VID/PID but not location_id=0x%08x\n", loc);
fprintf(stderr, "Hint: confirm Location ID via system_profiler SPUSBDataType\n");
return 4;
}
// Output report: 1 byte, bit0=NumLock ON (0x01)
// IOHIDDeviceSetReport is the API to send output/feature reports :contentReference[oaicite:7]{index=7}
uint8_t report[1] = { 0x01 };
r = IOHIDDeviceSetReport(target, kIOHIDReportTypeOutput, 0, report, sizeof(report));
if (r != kIOReturnSuccess) {
fprintf(stderr, "IOHIDDeviceSetReport failed: 0x%08x\n", r);
return 5;
}
printf("NumLock LED ON report sent to location_id=0x%08x\n", loc);
free(list);
CFRelease(devs);
CFRelease(mgr);
return 0;
}
GitHubにもソースコードを置きました。
ソースコードをビルドします。
$ clang hid_set_numlock.c -o hid_set_numlock -framework IOKit -framework CoreFoundation
hid_set_numlockコマンドを実行してPC入力モードへの切り替えに成功しました。
$ ./hid_set_numlock 0x1267 0x6523 0x01100000
Matched device: location=0x01100000 product=(null)
NumLock LED ON report sent to location_id=0x01100000
さいごに
USBデバイスを制御するHIDコマンドを少し理解できました。
