Why I'm Doing This
I run Fedora 43 with Hyprland. My keyboard is an AULA F75. The software to configure it, RGB effects, speed, macros, is Windows-only. There is no Linux support, no open protocol spec, nothing.
The keyboard has a "Ripples_shining" effect I like. It lights up from a keypress and spreads outward like a wave. The problem is the speed slider only goes from 0 to 4, and even at maximum it feels sluggish. I want faster. I want to push values the UI doesn't allow.
This post is not a success story. I haven't cracked it yet. But I've learned a lot about how this keyboard talks to the OS, and I'm documenting everything here so I don't lose the thread, and in case someone else is doing the same thing.
The Setup
- Fedora 43, Hyprland, kernel 6.x
- AULA F75 (VendorID:
258a, ProductID:010c) - Wine 11.0 Staging
- Connected via USB-C (wired mode only, wireless is a separate problem)
Step 1: Getting the Software Running Under Wine
The AULA software needs raw HID access via /dev/hidraw. By default, the hid-generic kernel driver claims the device and blocks this. The fix is a udev rule.
Find your keyboard's IDs:
lsusb
# Bus 001 Device 003: ID 258a:010c BY Tech Gaming KeyboardCreate the rule:
sudo nano /etc/udev/rules.d/99-aula-f75.rulesSUBSYSTEM=="usb", ATTRS{idVendor}=="258a", ATTRS{idProduct}=="010c", MODE="0666"
KERNEL=="hidraw*", ATTRS{idVendor}=="258a", ATTRS{idProduct}=="010c", MODE="0666"Reload:
sudo udevadm control --reload-rules && sudo udevadm triggerVerify:
ls -la /dev/hidraw*
# should show crw-rw-rw-The F75 exposes two hidraw nodes. Confirm which belong to the keyboard:
for f in /dev/hidraw*; do
echo -n "$f: "
udevadm info --attribute-walk --name=$f | grep -E "idVendor|idProduct" | head -2
doneMine: /dev/hidraw1 and /dev/hidraw2 both show 258a:010c.
Install Wine, reset the prefix if needed, and run:
rm -rf ~/.wine # if you get kernel32.dll errors
WINEARCH=win64 wineboot --init
wine ~/Downloads/AULA_F75_Setup.exe
wine ~/.wine/drive_c/Program\ Files\ \(x86\)/AULA/F75/OemDrv.exeIt opens. The keyboard is detected. Effects apply. Wine's hidraw integration works fine for this, this part is solved.
Step 2: Capturing the Packets
Plan: use usbmon and tshark to capture USB traffic while moving the speed slider.
sudo modprobe usbmon
sudo tshark -i usbmon1 -w /tmp/aula_capture.pcapng
# move slider in AULA software, Ctrl+CExtract HID payloads:
tshark -r /tmp/aula_capture.pcapng \
-Y "usb.device_address == 3 && usb.transfer_type == 0x02 && usb.dst == \"1.3.0\"" \
-V 2>/dev/null | grep "Data Fragment"This worked once. I got real SET_REPORT control transfers:
bRequest: SET_REPORT (0x09)
wValue: 0x0306 (Report ID 6, Feature type)
wLength: 520Three packets per Apply. 520 bytes each, zero-padded. Diffing the packets between min and max speed:
# Only one byte differs:
# index 79 (0x4F): min speed = 0x40, max speed = 0x00Speed is a single byte. 0x40 at slider position 0 (slowest), 0x00 at position 4 (fastest). Linear steps of 0x10. Lower value = faster. The UI is already sending the minimum possible value (0x00) at its maximum setting, so there's nothing to unlock by going below, unless the firmware wraps or interprets 0x00 as "default" rather than true zero.
The problem: every subsequent capture returned empty output. Wine's HID backend is not consistent. Sometimes it routes through the USB stack (visible to usbmon), sometimes it bypasses it entirely and writes directly to hidraw. You can't rely on usbmon alone.
Step 3: What the HID Descriptor Says
sudo usbhid-dump -a 1:3 2>/dev/nullRelevant section for Report ID 6:
85 06 → Report ID = 6
75 08 → field size = 8 bits
96 07 02 → report count = 519 bytes
B1 02 → Feature reportDevice expects: Report ID 6, Feature type, 519 bytes payload = 520 bytes total. Matches the control transfer capture.
Step 4: Trying to Send Packets Directly
The correct ioctl for HID Feature Reports on Linux:
# _IOC(READ|WRITE, 'H', 0x06, 520)
HIDIOCSFEATURE = 0xC2084806Script that sends all three captured packets in sequence:
import fcntl, time
HIDIOCSFEATURE = 0xC2084806
PKT1 = bytes.fromhex("0684000001008000...") # init/handshake
PKT2 = bytes.fromhex("0604000001008000...") # effect config
PKT3 = bytes.fromhex("060a000001000002...") # color data
def pad(pkt):
return bytes(pkt) + b'\x00' * (520 - len(pkt))
def send(path, pkt):
with open(path, 'rb+', buffering=0) as f:
fcntl.ioctl(f, HIDIOCSFEATURE, bytearray(pad(pkt)))
for speed in [0x40, 0x20, 0x00, 0x01, 0x05, 0xff]:
pkt2 = bytearray(PKT2)
pkt2[79] = speed
send('/dev/hidraw2', PKT1)
send('/dev/hidraw2', bytes(pkt2))
send('/dev/hidraw2', PKT3)
time.sleep(4)Result: ioctl succeeded, something happened on the keyboard, but it was the wrong effect. The ripple changed to something else entirely and speed was identical across all values.
The packets from capture session 1 were probably not from Ripples_shining, I don't know what effect was active during that capture. And even if they were, something about the packet content or sequence is wrong.
Step 5: strace -> Where Things Got Complicated
Since usbmon was unreliable, next approach: strace to intercept the actual syscalls Wine makes when writing to hidraw.
Find which Wine process holds the hidraw fds:
for pid in $(ps aux | grep -i wine | grep -v grep | awk '{print $2}'); do
echo "=== PID $pid ==="
ls -la /proc/$pid/fd 2>/dev/null | grep hidraw
doneFound it: the winedevice process holds:
- fd 51 →
/dev/hidraw1 - fd 52 →
/dev/hidraw2
Attaching strace on write to those fds returned nothing. Tried following threads with -f, tried all fd numbers, still nothing useful coming through.
The discovery (from continuing the investigation separately) is that Wine uses writev() with internal pipes, not direct write() to the hidraw fd. The real 520-byte HID payload appears inside a writev iov buffer:
writev(...)
iov_len=520 ← this is the real device packetOther sizes (64, 40, 12 bytes) in the strace output are Wine's internal message framing, not device data. The filter was wrong the whole time.
Step 6: What the Real Packets Look Like
Using writev interception, two real 520-byte payloads were extracted, pkt_0.bin (low speed) and pkt_1.bin (high speed). Diffing them:
Only ONE byte changes:
offset 109: 0x47 → 0x07No checksum changes. No header changes. Just that one byte inside what appears to be a repeated pattern block:
...09 47 09 47 09 47...
→
...09 07 09 47 09 47...So the speed value isn't a standalone "speed field", it's embedded inside a table or pattern structure. Modifying only offset 109 and sending the packet had no visible effect on the keyboard. The firmware either ignores isolated single-byte changes, or requires multiple entries in the pattern to be updated, or needs a separate commit packet after the config.
Where Things Stand
| Thing | Status |
|---|---|
| Wine runs the software on Fedora | ✅ |
| udev rule for hidraw access | ✅ |
| HID descriptor parsed | ✅ Report ID 6, 520 bytes |
| Speed byte identified (index 79 from usbmon, index 109 from writev) | ⚠️ conflicting |
| Reliable packet capture method | ❌ |
| Sending correct effect-specific packets | ❌ |
| Speed values beyond UI limits tested | ❌ |
The two different byte indices (79 from the usbmon capture, 109 from the writev capture) are suspicious. They might be from different packet types, or the usbmon capture was from a different effect entirely. I need a clean, confirmed capture of Ripples_shining specifically at all five speed positions.
Current Hypotheses
Speed is probably not a simple single-byte field. Leading theories:
- Pattern table encoding -> the speed value is repeated across multiple entries in a lookup table. All entries need to change together.
- Derived encoding -> the byte value is computed from the speed setting rather than stored directly. Need to understand the formula.
- Missing commit packet -> there's a separate "apply" packet that triggers the firmware to use the new config. Without it, the keyboard ignores the data.
- Wrong interface -> the config might need to go to hidraw1, not hidraw2, or through a different report ID entirely.
What's Next
- Clean strace
writevcapture specifically for Ripples_shining at each slider position (0, 1, 2, 3, 4) - Diff all five to understand the full encoding pattern
- Test whether sending the complete unmodified packet for Ripples_shining (no speed change) reproduces the correct effect
- If that works, start modifying values and watching the keyboard
The udev rule and Wine setup are documented and reproducible. The packet capture methodology is mostly figured out. The missing piece is a clean, controlled, effect-specific capture session.
Will update this when I get further.
Running the Software (TL;DR)
If you just want to use the AULA software on Fedora with Wine:
# udev rule (one-time setup)
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="258a", ATTRS{idProduct}=="010c", MODE="0666"
KERNEL=="hidraw*", ATTRS{idVendor}=="258a", ATTRS{idProduct}=="010c", MODE="0666"' \
| sudo tee /etc/udev/rules.d/99-aula-f75.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
# run the software
wine ~/.wine/drive_c/Program\ Files\ \(x86\)/AULA/F75/OemDrv.exeTested on Fedora 43, Wine 11.0 Staging, wired USB-C. Wireless mode needs a separate udev rule for the dongle's VID/PID.