the un i blog

1. THE BUTTON - short summary of tonight’s efforts

i just acquired a IRIScan™ Desk 5 Pro book scanner and it comes with an extra button. like, not within the scanner, another whole USB device. Just a single button it’s very funny.

button.jpg

Figure 1: i took this image with the scanner using ffmpeg i feel like making this work properly might take a while still

but none of this formally supports linux in any shape or capacity so i had to make it work myself. The end results are on github!!

of course the first thing you try is pressing the button. expectedly, it does absolutely nothing.
running lsusb yields something marginally useful, namely a vendor id and a product id, but a, uh, rather interesting name:

...
Bus 001 Device 002: ID 046d:c05a Logitech, Inc. M90/M100 Optical Mouse
...
Bus 001 Device 056: ID 2e5a:2015
...

So actually, the button is not only functionally useless so far, it is also nameless!

There’s a couple standard things you check with new usb devices on linux - /dev/input/by-id/ is a classic, but it does not show up there. Neither does it just appear to be one of the /dev/input/event* devices - at least that’s what libinput told me, and libinput is my friend who would never lie to me!!!!
So there’s not really much left, eventually I arrive at /dev/usb and finally, two previously undiscovered devices appear - /dev/usb/hiddev0 and /dev/usb/hiddev1. Even after a lot of looking up various stuff, I’m not quite sure what the first one is, but it appears to be my keyboard. Anyways, that gives us a decent chance at doing something, and indeed, sudo cat /dev/usb/hiddev1 spews out some meaningless garbage every time I press the button. It also showed me that the polling rate of this thing is about 3 Hz. Anyways, it’s always the same 512 bytes:

0000000 0002 ff00 0000 0000 0002 ff00 0001 0000
0000010 0002 ff00 0002 0000 0002 ff00 0003 0000
0000020 0002 ff00 0004 0000 0002 ff00 0005 0000
0000030 0002 ff00 0006 0000 0002 ff00 0007 0000
0000040 0002 ff00 0008 0000 0002 ff00 0009 0000
0000050 0002 ff00 000a 0000 0002 ff00 000b 0000
0000060 0002 ff00 000c 0000 0002 ff00 000d 0000
0000070 0002 ff00 000e 0000 0002 ff00 000f 0000
0000080 0002 ff00 0010 0000 0002 ff00 0011 0000
0000090 0002 ff00 0012 0000 0002 ff00 0013 0000
00000a0 0002 ff00 0014 0000 0002 ff00 0015 0000
00000b0 0002 ff00 0016 0000 0002 ff00 0017 0000
00000c0 0002 ff00 0018 0000 0002 ff00 0019 0000
00000d0 0002 ff00 001a 0000 0002 ff00 001b 0000
00000e0 0002 ff00 001c 0000 0002 ff00 001d 0000
00000f0 0002 ff00 001e 0000 0002 ff00 001f 0000
0000100 0002 ff00 0020 0000 0002 ff00 0021 0000
0000110 0002 ff00 0022 0000 0002 ff00 0023 0000
0000120 0002 ff00 0024 0000 0002 ff00 0025 0000
0000130 0002 ff00 0026 0000 0002 ff00 0027 0000
0000140 0002 ff00 0028 0000 0002 ff00 0029 0000
0000150 0002 ff00 002a 0000 0002 ff00 002b 0000
0000160 0002 ff00 002c 0000 0002 ff00 002d 0000
0000170 0002 ff00 002e 0000 0002 ff00 002f 0000
0000180 0002 ff00 0030 0000 0002 ff00 0031 0000
0000190 0002 ff00 0032 0000 0002 ff00 0033 0000
00001a0 0002 ff00 0034 0000 0002 ff00 0035 0000
00001b0 0002 ff00 0036 0000 0002 ff00 0037 0000
00001c0 0002 ff00 0038 0000 0002 ff00 0039 0000
00001d0 0002 ff00 003a 0000 0002 ff00 003b 0000
00001e0 0002 ff00 003c 0000 0002 ff00 003d 0000
00001f0 0002 ff00 003e 0000 0002 ff00 003f 0000

No idea whether there’s any significance to them, I doubt it, but it’s nice to have something to rely on.

The first thing I had to figure out was how to make the button more permanent - /dev/usb/hiddev1 is a fairly volatile spot for anything to be on, and it might not last a reboot. This was fairly easy to achieve with a symlinking udev rule:

ACTION=="add", ATTRS{idVendor}=="2e5a", ATTRS{idProduct}=="2015", SYMLINK+="button", MODE="0666"

Storing this in /etc/udev/rules.d/50-button.service was sufficient to turn /dev/button into a more permanent and easily readable outlet for your favorite 217 bits.

Actually waiting for the button to be pressed was a little more annoying though and made me turn to C (side note: during the whole development process, not a single segfault occurred i hope youre proud of me…..).

First I tried just reading 512 bytes and more or less repeating if there wasn’t anything to be read. But this is not very efficient obviously so eventually I turned to poll, a decently nice syscall that tells you when there’s new stuff to be read and waits for a certain while. But not all bytes are sent at the exact same point in time, so I have to wait a slight bit until I can read 512 at once.

At some point I got that more or less figured out, despite some struggles with finding the right thing to call and the right options to open and so on. Yet every time I pressed the button a couple times in a row, the program would hang and only read further when I sent SIGINT or something. Why was that? Well, since I actually want to stuff with the button presses, after every successful read I call a program via system("~/.local/bin/button-pressed"). Oops, turned out that system is blocking and starting a shell and launching a KDE notification (which is was that script did at the time) takes a decent bit such that the events kind of queue up.
Nonetheless, this was decently easy to fix by just asynchronously calling the thing.

So, at some point I got a tool that worked sufficiently well at detecting these button presses and calling something. Following the same laws of thought as every person who would write 80 lines of C for their button, I wasn’t quite satisfied yet and felt the need to automate some stuff further. So I turned to a systemd service. There was the easy option of just enabling and autorestarting it, and that worked fairly well, well, unless I disconnect the button (also it would waste performance by restarting the thing every idk 20 seconds), in which case reconnecting it would introduce delay until the autorestart and just feel kinda ugly either way.

So I turned to the even sillier alternative and changed my udev rule:

ACTION=="add", ATTRS{idVendor}=="2e5a", ATTRS{idProduct}=="2015", SYMLINK+="button", MODE="0666", TAG+="systemd", ENV{SYSTEMD_USER_WANTS}="the-button.service"

With this rule, the service is enabled as long as the device is connected. But this yielded even more mysterious issues: every time I connected the thing, the instance of the program started immediately after would even immediatelier crash due to receiving a 64 byte long message on the first button press (every button press takes 512 bytes, remember?). But if I were to restart the service with or without pressing the button in the meantime, the new instance would work just fine.

It took me another half an hour to figure that out, but the problem was fairly simple all along - /dev/button seems to behave differently the first couple milliseconds after the service gets started, so while for some reason it detects the button presses anyways, it probably reads something different? I’m not entirely sure, but there was a pretty easy fix - adding 300 ms of delay before opening the file seems to work fine and it’s not like you were gonna press it that quickly anyways.

so yeah, now it works fairly well, and I guess I’ll use it to speed up the scanning process or something, which probably deserves another entry uhh thank you

2. THE GLOWING BUTTON - summary of day two’s efforts

turns out the button also has a red LED and when I run the company’s software with wine (for which I needed to use 7zip to extract the data from the self-extracting setup because that setup wouldn’t work but it’s only a setup for multiple setups and the individual setups all work), the LED starts glowing. how does that work? well I had no clue so I googled something like “linux monitor usb” until I eventually arrived at usbmon, a kernel feature that allows you to do exactly that. By enabling the kernel module, doing a bunch of stuff and eventually running the program while watching the usbmnon output for the right interface, I figured out the LED gets enabled by the following 32 bytes:

0x30, 0x31, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30,
0x00, 0x3e, 0x00, 0x0d,
0x00, 0xa0, 0x00, 0x20,
0x00, 0x20, 0x00, 0x3c,
0x00, 0x21, 0x00, 0x2d

As I later figured out by messing around a bit, exchanging the first 0x31 for a 0x30 disables it.

So how do I actually send these bytes to the device? there’s obviously no particularly user-friendly interface to do that, since neither the average developer nor the average person with a lot of time on their hands needs to write USB drivers (or something like them) on the regular. Nonetheless I quickly arrived at libusb, which came the closest to user-friendly this stuff gets I suppose.

By a lot of googling (and honestly some LLM help i’m sorry it’s a bit embarassing but just way faster and i proof readall of it trust me please), i wrote a program that just sends these 32 bytes to the right interface and the right endpoint!! the latter is also very important but at least it appears in both lsusb -v output and the usbmon output. nonetheless, it wouldn’t work immediately - since the button is also treated as a HID device, there’s already a driver loaded for it which blocks libusb from properly acquiring the interface, luckily, it provides a function to temporarily detach and reattach the driver. Finally, I was able to just run button-light on and button-light off, and have it work.

But this caused a couple other problems, since the other program to watch the button presses breaks in the meantime because the file descriptor to /dev/button/ becomes invalidated due to the driver being unloaded blah blah blah. This doesn’t seem easily fixable and I’m not super inclined on reopening the file within the program because of a lot of decently fucked up race conditions and so on, so I took the substantially more cursed route of only processing one input per run of button: It waits for a single button press, runs button-pressed, which turns the light on and off, then uses exec to launch itself again, avoiding any of these mishaps. This works well and while it obviously leaks PIDs, as far as I know it shouldn’t cause any permanent issues.

Date: 2024-07-30 Di 00:00