Category Archives: general

KeyLight v0.0.1

Menu showing the configuration interface for KeyLight, with sliders for selecting the weighting of different light sensors, and for selecting the mapping of light level to keyboard backlight brightness.

I think I’ve finally done it — written an application for a “total addressable market” size of 1, namely me.

Usually, I keep the backlight on my Realforce GX1 keyboard on the lowest of its 3 brightness settings. But sometimes the sun gets a bit bright outside, and I go up a notch or two.

This app automates that process by reading the Apple Pro Display XDR’s ambient light sensors (ALS), computing a user-driven average, and then flexibly mapping that to the 3 keyboard backlight brightness levels. It also turns off the backlight if you want when the display goes dark or the Mac sleeps, since otherwise it will still be lit up during dark wake (or not yet asleep) time.

I had written the core of the RealforceControl library last year, since Realforce only provides Windows software for interacting with the non-core features of the keyboard. It was a fun experiment in reversing the USB commands the official software uses for configuring the keyboard.

Then, this summer, sun happened a fair bit, and I was minorly annoyed at having to push a button during the day for keyboard brightness up, and in the evening again for keyboard brightness down. And then realized that I was thankful for Apple’s automatic display brightness working so well on the Pro Display XDR that I didn’t have to deal with that aspect at all.

And so, I looked into whether one could access the data from the ambient light sensor of the display, and it also happens to be exposed as a USB HID device (the relevant info I could find was for a Windows-based app for it, of all things). That worked quite well (other than an annoying CoreHID warning I cannot seem to get rid of when requesting an explicit element update), and so now I have an AmbientLight library for reading this sensor data.

And now, there’s KeyLight, which is a small macOS app that glues the two together, and now regulates the brightness of the keyboard. 🥳

Note: I used a locally hosted Qwen 3.8 27B for the later parts of these developments: I’m not sure I want to learn SwiftUI, and Swift’s actor model really has a lot of sharp edges; and as usual, Apple’s documentation leaves me wanting. The LLM has been a mostly competent aid, and I’m not sure I would have persisted with it otherwise… It was an interesting learning experience.

Games of 2022

Last year has been pretty much the year of the life service game for me:

  • Still playing Destiny 2: The Witch Queen campaign was great, and the seasons are OK but predictable and getting a bit stale.
  • Started playing Genshin Impact (on PS5, not mobile), which in spite of my initial exploitative gacha hesitation contains a good (as well as large and growing) exploration game somewhere. It’s way too wordy in its dialog, but the actual mechanics and game itself is sound. After catching up on all the existing story content, I’m playing this in “maintenance mode” between content patches.
  • Also got hooked on Marvel SNAP (also playing this mainly on my M1 Mac rather than on phone / tablet). First time I’m playing one of these collectible card games. It strikes a really nice balance between simplicity and randomness, but also contains a surprising amount of depth and variability, and I’m having a good time even though I’m usually PvP / duel averse. The onboarding with the increasingly complex and larger card pools is well done.

So yeah, that’s the three season passes I’m currently paying for… 🥲

Other than that, I have (of course 😉) played the known quantities of Elden Ring, Horizon: Forbidden West, and God of War: Ragnarok, although I haven’t finished GoW yet, since I somehow rather felt like playing superhero card games…

More (mechanical) keyboards and Mac key remapping

After browsing too much r/MechanicalKeyboards I bought some more keyboards… 😀

One is a white TKL CODE with 65g Zealios switches
White TKL CODE with 65g Zealios switches
(which is essentially the same as my current WASDv2 except for the case color, switches and lighting), which I bought because I was interested in the Zealios switches, and this is one of the few pre-made keyboards that offer them.

The other was a birthday present to myself and is a Leopold FC660C (Topre) in Grey & Blue:Leopold FC660C (Topre) in Grey & BlueThe Leopold does not have a Mac specific mode (although you can use its DIP switches to swap the Windows and Alt key, but that doesn’t get you ⌘ to the right of the space bar).

Therefore I fiddled a bit with remapping some keys for these keyboards for macOS use. One option for the Leopold FC660C is Hasu’s Alt Controller which replaces the controller board itself with a fully programmable one. Another option is to use Karabiner to remap the keys at the software level, but I found the CPU usage of Karabiner’s karabiner_grabber process a bit too high for what it should be doing. Since macOS 10.12 there’s another way to remap keys using hidutil.

My configuration (for both keyboards) looks as follows (stored inside ~/bin/remap.sh which is run on login, based on this script):

#!/usr/bin/env bash
# https://developer.apple.com/library/content/technotes/tn2450/_index.html
# https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-1035.41.2/IOHIDFamily/IOHIDUsageTables.h.auto.html
# https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-1035.41.2/IOHIDFamily/AppleHIDUsageTables.h.auto.html
FROM="\"HIDKeyboardModifierMappingSrc\""
TO="\"HIDKeyboardModifierMappingDst\""

ESCAPE="0x700000029"
CAPS_LOCK="0x700000039"

PRINT_SCREEN="0x700000046"
SCROLL_LOCK="0x700000047"
PAUSE="0x700000048"
INSERT="0x700000049"

F13="0x700000068"
F14="0x700000069"
F15="0x70000006A"

LEFT_ALT="0x7000000E2"
LEFT_GUI="0x7000000E3"
RIGHT_CTRL="0x7000000E4"
RIGHT_ALT="0x7000000E6"
RIGHT_GUI="0x7000000E7"
PC_MENU="0x700000065"

MEDIA_PLAY="0xC000000B0"
MEDIA_NEXT="0xC000000B5"
MEDIA_PREV="0xC000000B6"
MEDIA_EJECT="0xC000000B8"

# WASDv2 / CODE in Mac mode
hidutil property --matching '{"ProductID":0x269, "VendorID":0x4d9}' --set "{\"UserKeyMapping\":[
{$FROM: $CAPS_LOCK,    $TO: $ESCAPE},
{$FROM: $INSERT,       $TO: $MEDIA_PLAY},
{$FROM: $F13,          $TO: $MEDIA_EJECT},
{$FROM: $F14,          $TO: $MEDIA_PREV},
{$FROM: $F15,          $TO: $MEDIA_NEXT},
]}"
# Leopold FC660C
hidutil property --matching '{"ProductID":0x134, "VendorID":0x853}' --set "{\"UserKeyMapping\":[
{$FROM: $CAPS_LOCK,    $TO: $ESCAPE},
{$FROM: $LEFT_GUI,     $TO: $LEFT_ALT},
{$FROM: $LEFT_ALT,     $TO: $LEFT_GUI},
{$FROM: $RIGHT_ALT,    $TO: $RIGHT_GUI},
{$FROM: $RIGHT_CTRL,   $TO: $RIGHT_ALT},
{$FROM: $PC_MENU,      $TO: $RIGHT_CTRL},
{$FROM: $INSERT,       $TO: $MEDIA_PLAY},
{$FROM: $PRINT_SCREEN, $TO: $MEDIA_EJECT},
{$FROM: $SCROLL_LOCK,  $TO: $MEDIA_PREV},
{$FROM: $PAUSE,        $TO: $MEDIA_NEXT},
]}"

Mainly this maps the modifier keys to be more Mac-like, caps lock to escape (which means I can run the Leopold’s escape key permanently in ` / ~ mode (using Fn + Q), as well as some media keys. The links in the header-comment of the script contain lists of supported pages and usages; the page is the upper 32-bit word and the usage the lower 32-bit word.

If you want to run this on login, you can use launchd by storing a plist in ~/Library/LaunchAgents/de.maven.remap_keys.plist for example containing

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>de.maven.remap_keys</string>
    <key>ProgramArguments</key>
    <array><string>/Users/maven/bin/remap.sh</string></array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

Happy hacking!

SoundPusher v1.0.0

And we have a release (which is fairly untested, but it works for me). This is first time I’ve released something post-Gatekeeper, so I hope I signed all the things correctly. Also the first time I’ve attempted an installer.

I’ve redone the synchronisation between the input and output thread about 5 times now, let’s hope this variant works out. It’s easy if you don’t care about latency, but trying to get that as low as possible without glitching and staying real-time is quite hard. By default, only AC3 encoding is enabled as DTS support in libavcodec is only experimental. EAC3 seems to distribute packets over multiple SPDIF frames, and thus seems to be a bad choice due to further increased latency.

Diablo 3 sounds quite different with 5.1 sound for example, as the different screen corners map to in front or behind your character (and thus the listener) due to the isometric perspective.

Report any issues or suggestions on the GitHub page. Happy multi-channel audio-ing! 🙂

SoundPusher

I’ve been on a bit of a coding spree during my time off work, and managed to make some progress on my old attempt to provide a virtual 5.1 sound-card that does real-time AC3 encoding to a digital output connection. This time around, I didn’t actually stop a third of the way in and it actually works (and quite well at that — at least on my system — if I may say so).

The code is on GitHub. If you’re interested helping with testing, feel free to get in touch.

For some internals, I reduced the number of components to two, as I eschewed AudioUnits (and thus the separate encoder) altogether. There’s now just the user-space LoopbackAudio driver (AudioServerPlugIn) and the SoundPusher application that reads from the loopback-driver, compresses that and then sends it to the real digital output stream. Latency is reasonable from my experiments, as is CPU usage.

Things I learnt about my Synology NAS

The following points have been observed on my Synology DS414, but I would think that most of them are due to the software stack, and not hardware specific.

  • DDNS updates with FreeDNS require explicit use of username and password, instead of allowing use of the authentication token.
  • DDNS update interval seems to be hardcoded and not configurable (through the DSM UI). I’d prefer the NAS remain hibernating at night.
  • The AFP (Apple Filing Protocol) implementation is based on the old-ish version 2.2.3 of Netatalk. Netatalk requires a separate database to record file to id mappings (managed by cnid_dbd) which is purged by the NAS on every reboot. This database is created as soon as a user connects to the volume via AFP and can take multiple hours to build if the volume contains many files. This makes all other accesses quite slow while the database is rebuilt.
  • Hibernation leaves fans running.
  • If your AFP connections to the NAS reproducibly crash and disconnect (with very flaky behaviour on the Mac, no clean dismount, endless reconnect retries and replays “AFP_VFS afpfs_DoReconnect” when attempting to create a Spotlight index on the NAS volume, a likely cause is problematic extended attributes on files on the volume. These can be removed (after ssh’ing onto the NAS and going to the correct shared folder) with 
    find . -type d -name "@eaDir" -print0 | xargs -0 rm -rf