REALFORCE GX1 Keyboard on the Mac

I’ve been keeping up with a bit of tradition and got myself a(nother) keyboard for my birthday. The selection criteria was relatively easy:

  • Topre switches (which I really enjoy on my Leopold FC660C, which remains my main driver at work)
  • Backlit (I’ve used this more than I thought I would on my TKL CODE w/ 65g Zealios, which is what I’m looking to replace with a Topre board — RIP WASD)
  • TKL (Ten-Key-Less)

Since Topre keyboards seem to be very much falling out of favor, there is exactly one option I could find: The Realforce GX1 “Gamer” (sigh) keyboard. This model is not listed as supporting macOS (in contrast to most of Realforce’s other keyboards). Nonetheless I gave this a go. I even went for a limited Hatsune Miku collab edition, since it had PBT keycaps (instead of ABS on the normal model), I like cyan and pink, it was one of the few models that shipped to Germany, and it was on sale.

I ordered the keyboard from Amazon Japan on a Monday, and it arrived the same week Friday. That is still somewhat insane to me…

It is a very nicely built keyboard and as solid as the Leopold (which is rock-solid) — no flex, no bend. In contrast to the Leopold, it features a floating key design, but that doesn’t seem to impact the nice Topre “thock” sound that I like.

In a somewhat unusual move for a Topre keyboard the switches and keycaps are Cherry MX compatible, albeit with the LED / backlight above the stem. The key legends are simple, and thankfully don’t use one of those (IMO) rather ugly “gamer” fonts. I was a bit worried about the legibility of the legends on the white keycaps with a white backlight, but it’s a non-issue in person (as long as the backlight is on).

It is possible to “de-gamerify” the keyboard a bit, but for that (and key remapping) you need to use the Windows (only) software — at least once (note: Realforce’s Mac software will not recognize the keyboard at all). But it is possible to both flash the keyboard firmware and configure the keyboard on Mac computers with Apple silicon. For that, I used UTM (a system emulator and virtual machine host) and virtualized Windows 11 ARM in order to run the REALFORCE Software for Windows. Once Windows 11 and the Realforce Software is installed, you can then pass-through the USB keyboard device so that the software will be able to find it.

The firmware update is a bit iffy, since it reconfigures and reconnects the USB device (presumably to put it into the firmware update mode), which means you need to re-enable pass-through for the USB device again in UTM. I’d recommend never physically disconnecting the keyboard, but I did force restart UTM / Windows after initiating the firmware upgrade mode, and it then completed successfully on that second run.

As for configuration, I

  • set the backlight to Pure White and disabled the boot up pattern
  • swapped the Win- and Alt-keys (not that there’s a great need to do this since you can also do this on the macOS side) since I had the CODE keyboard set up that way as well (using its DIP switches for Mac mode),
  • added / modified a few Fn key combinations: This was mainly placing media keys on the Editing keys (as they were on the CODE) and moving the existing shortcuts that I replaced there to alphanumeric keys. For additional compatibility with how my CODE keyboard was set up, I set Fn+F11 to Backlight Brightness, Fn+PrtScn to Eject (this allows for easy sleep etc. shortcuts), and Fn+Pause to Mute. I also added the Easy Macro keys to Fn+F9/F10 as well (since it made sense to put them next to the other preconfigured macro keys — not that I currently have any use for them).
The Configuration of the Fn Key Shortcuts in the REALFORCE Windows Software
Realforce GX1 Modified Fn Keys for (my) Mac Use

After those settings are saved to the keyboard they’re retained and the software is not needed anymore — unless you change your mind about any of those settings. All in all, I’m very happy with the keyboard (but I am also used to paying a fair amount of money for somewhat exotic keyboards).

On top of that, I’m still using a minor software remap to put Esc on Caps Lock (which I’ve become very accustomed to due to the Leopold FC660C in addition to the awkward Macbook Pros with the Touchbar), see the other post for details, but here’s my current script for completeness:

#!/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"

# REALFORCE GX1 (Hatsune Miku) X1UD w/ Win & Alt swapped
hidutil property --matching '{"ProductID":0x317, "VendorID":0x853}' --set "{\"UserKeyMapping\":[
{$FROM: $CAPS_LOCK,    $TO: $ESCAPE},
{$FROM: $INSERT,       $TO: $MEDIA_PLAY},
{$FROM: $F13,          $TO: $MEDIA_EJECT},
]}"
# 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},
]}"
# 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},
]}"

CoreAudio Taps for Dummies

I’ve finally spent some time on implementing CoreAudio taps in SoundPusher 1.5. These let you listen in on (“tap”) the output that is generated by certain processes or sent to certain devices.

So far, SoundPusher has done this with a loopback device that reflects any output it receives back into an input stream that looks to the system like a (6-channel) microphone. This has required increasingly more permissions from the system to access microphone input (since the OS doesn’t know what the SoundPusher Audio device records), and results in a big orange blob in the menu bar to warn the user that they’re being recorded.

Apple has started to provide APIs for recording system audio through audio “taps”, and if they’re good enough for Rogue Amoeba than maybe they’re good enough for me as well.

Unfortunately, Apple’s developer documentation is terrible as usual even though the actual C headers contain some more information. So this is mostly what I’ve figured out.

Taps work by attaching them to an aggregate device using the kAudioAggregateDeviceTapListKey in the dictionary provided to AudioHardwareCreateAggregateDevice(). See https://github.com/q-p/SoundPusher/blob/v1.5.1/SoundPusher/AudioTap.mm#L52 for an example.

The aggregate device with the tap (and nothing else — initially I put the device I was tapping into the aggregate device as well but that only confused matters) will then provide an input stream that contains the tapped data. This is similar to reading from a (virtual) microphone, so I didn’t have to change the rest of the code much. Starting to read from such a tap requires a TCC / privacy authorization and results in a less obnoxious purple dot compared to the orange microphone blob. There doesn’t seem to be a way to query this authorization status as is possible for the microphone access using [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio], so if the user denies this request all you will get is silence (and you cannot inform them about the authorization status since you don’t know).

Do note that if the device you’re tapping also contains input streams (like my SoundPusher Audio loopback device did), then you will in addition still need microphone access to tap that device. I have tried all sorts of ways to disable the input streams to avoid the microphone permissions when tapping a device with inputs, but no such luck. That’s why I have removed all input (and therefore loopback) support from SoundPusher Audio.

Why is this dummy device then needed at all? Because I need 5.1 channels of input data, and unless the user has a real 6 channel audio device, there won’t be anything good to tap. The system can provide a mono or stereo mix-down when tapping, but if you want something more specific than that, then you need a real (fake) device to prescribe the shape and format you want tapped…

Synology DS923+ DSM Memory Compatibility Warnings

Synology’s OS warns when encountering untested memory configurations on most newer hardware, and the only configurations that are tested are the official expansion modules (which actually ship different chips depending on availability) D4ES02-4G, D4ES02-8G, and D4ES02-16G.

They ship and update a list of the compatible modules in /var/lib/memory-compatibility/ds923+_mem_host.db which is just a (compacted) JSON file (which can be pretty-printed using jq . ds923+_mem_host.db):

{
  "success": 1,
  "list": [
    {
      "model_number": "Blacklist",
      "recommend": true,
      "manufacturer": "[\"0198\",\"Kingston\",\"014F\",\"Transcend\"]"
    },
    {
      "model_number": "D4ES02-4G",
      "recommend": true,
      "part_number": "M4DE-4GSSPC0M-FA14",
      "manufacturer": "[\"86F1\",\"Innodisk\"]",
      "rank": "1",
      "speed": "3200"
    },
    {
      "model_number": "D4ES02-8G",
      "recommend": true,
      "part_number": "AD4B320038G22 BSSC",
      "manufacturer": "[\"86F1\",\"Innodisk\",\"04CB\",\"Adata\"]",
      "rank": "1",
      "speed": "3200"
    },
    {
      "model_number": "D4ES01-16G",
      "recommend": true,
      "part_number": "M4DE-AGS2PC0M-AA14",
      "manufacturer": "[\"86F1\",\"Innodisk\"]",
      "rank": "1",
      "speed": "3200"
    }
  ],
  "nas_model": "ds923+"
}

I’ve bought two Kingston KSM26SES8/16HC modules, since they were reasonably cheap. They work fine but cause said memory warning, since they’re not one of the configurations shipped as official memory expansions. But we can just edit the compatibility database to make that warning go away. To do that properly, we need the part_number of our memory modules, for which we use dmidecode -t memory:

Handle 0x001F, DMI type 17, 40 bytes
Memory Device
	Array Handle: 0x001E
	Error Information Handle: 0x0022
	Total Width: 72 bits
	Data Width: 64 bits
	Size: 16384 MB
	Form Factor: SODIMM
	Set: None
	Locator: DIMM 0
	Bank Locator: P0 CHANNEL A
	Type: DDR4
	Type Detail: Synchronous Unbuffered (Unregistered)
	Speed: 2667 MT/s
	Manufacturer: Kingston
	Serial Number: XXXXXXXX
	Asset Tag: Not Specified
	Part Number: 9965787-004.A00G    
	Rank: 1
	Configured Memory Speed: 2400 MT/s
	Minimum Voltage: 1.2 V
	Maximum Voltage: 1.2 V
	Configured Voltage: 1.2 V

And with that, we can add a new compatible module to the database:

    {
      "model_number": "D4ES01-16G*",
      "recommend": true,
      "part_number": "9965787-004.A00G",
      "manufacturer": "[\"Kingston\"]",
      "rank": "1",
      "speed": "3200"
    }

And after a reboot, no more warnings! Note that DSM occasionally updates this .db file, so you may want to keep both a backup of the original file as well as your changes, so you can easily reapply them after an update.

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…

2021 MacBook Pro (14-inch) Impressions

While working from home for most of 2020 and 2021 I’ve been using my 12-core (24-thread) Mac Pro for my usual C++ development. For the eventual return to the office (which is still a rather big question mark), I didn’t want returning there and working from a laptop to be such a huge step down, so of course I ordered one of the shiny new 2021 MacBook Pros. I went for the 14-inch for portability, as I’m mostly using it in clamshell-mode at my desk, and only occasionally when traveling (remember that?).

As I’m only really stressing the CPU, the 10-core “Pro” version with the small GPU is totally adequate, and the 32GB were enough memory to load all those cores when compiling. The extra memory bandwidth of the “Max” doesn’t help much when only loading the CPU.

The display is very nice (more dimming zones than the Pro Display XDR), but I hardly notice ProMotion (up to 120Hz refresh rate).

For some benchmarks, I compiled our largely C++ work project (using a different build system to Xcode — which seems to have trouble parallelizing large builds) on all cores (including hyper-threads on the Mac Pro):

  • 2019 Mac Pro (12-core, 24-thread; 96GB): 4m54secs
  • 2021 MacBook Pro (8+2-core, 32GB, 14-inch): 3m50secs
  • 2021 MacBook Pro (8+2-core, 32GB, 14-inch) low-power mode: 4m32secs

So that’s roughly 28% faster on the laptop. The fans become audible after ~2min on full load, but they aren’t as annoying as the ones on my previous 2018 i9 Intel MacBook Pro which spun up at the slightest provocation. The new MBP’s fans sound more like low-level white noise. With low-power mode enabled (which throttles the CPU), the fans do come on as well, but only spin at a barely audible level; resulting in a slow-down of 15% compared to normal operation (and still being faster than the Mac Pro).

Using clang on macOS to compile g++ / libstdc++ compatible binaries

clang++ by default compiles for system “native” C++ library. On macOS, that is LLVM’s libc++. But what happens if happen to have a lot of binaries (on in my case) libraries compiled with g++ using its own libdstdc++ library?

Why was I doing that in the first place? Work stuff is mostly Linux and therefore uses gcc, and the Apple-provided clang that ships with Xcode.app doesn’t support OpenMP, so I ended up building a lot of stuff with a homebrew-sourced gcc. But it turns out that gcc’s OpenMP support on macOS (in particular the implementation of the synchronization primitives in libgomp) is surprisingly slow (on Linux it’s much better). I ended up with a piece of OpenMP code whose performance was so bad, I need to cross check what the cause was, and so I installed a (non-Apple) clang with which I wanted to compile the code in question, but preferably without having to rebuild all my gcc-compiled (and therefore libstdc++-using) dependencies.

The following seems to work for a homebrew-installed gcc 9.3 and llvm (clang) 10:

CXX=/usr/local/opt/llvm/bin/clang++
CXXFLAGS="-stdlib=libstdc++ -stdlib++-isystem /usr/local/Cellar/gcc/9.3.0_1/include/c++/9.3.0/ -cxx-isystem /usr/local/Cellar/gcc/9.3.0_1/include/c++/9.3.0/x86_64-apple-darwin19"
LDFLAGS="-stdlib=libstdc++ -L /usr/local/Cellar/gcc/9.3.0_1/lib/gcc/9 -L /usr/local/opt/llvm/lib"

The last part of the link flags that add the library path back to the homebrew clang are only required so that that clang‘s OpenMP support can find its libomp support library.

This works unless you used something like libstdc++‘s implementation of std::call_once, because that needs internal symbols that are declared as __thread, which with how homebrew’s gcc is configurationed specifically or gcc on Darwin behaves generally used “emulated thread-local storage” (tls), which in turn requires mangling that __thread declared symbol differently 😔. clang even knows about that (see -femulated-tls), but when I tried to use that variant (which did indeed link without complaint) I got weird malloc errors in emutls functions, and it was easier for me to replace std::call_once with a hack than figuring out where those errors came from.

Mac Pro History

  • 2006-08
    • 2.66 GHz 2×2-Core Xeon
    • 4 GB 667 MHz DDR2 ECC
    • ATI Radeon X1900 XT 512 MB
    • 500 GB Serial ATA 3 Gb/s drive
    • 16x SuperDrive DL
    • Airport Extreme & BT 2.0+EDR
  • 2013-12
    • 3.5 GHz 6-Core Xeon
    • 32 GB 1866 MHz DDR3 ECC
    • 2x AMD FirePro D700 6 GB GDDR5
    • 512 GB PCIe-based SSD
  • 2019-12
    • 3.3 GHz 12-Core Xeon
    • 96 GB 2933 MHz DDR4 ECC
    • Radeon Pro 580X 8 GB GDDR5
    • 1 TB SSD

Cores went up, (base-)speed went down slightly (although the average turbo-boosted speed should be higher). Memory per thread went up from 1 GB to 4 GB (hello huge C++ templates). The graphics card will be replaced by the AMD Radeon Pro W5700X once available.