home

Partially Resurrecting the GearVR Controller

You ever dig out some old gadget from a drawer, think “hey, I could totally use it for something”, and then three days later you’re deep in a Rust code you never signed up for?

Yeah. Me neither. Until recently.

It started with a Samsung Gear VR Controller. It’s a nice piece of technology, a trigger, a decent touchpad, and enough sensors to be a VR controller, also some handy buttons. My goal was simple: use it on Linux (and Android) as a mouse/shortcut remote.

Should be simple, right?

Prototyping

I started where anyone should start: making an MVP. I decided to use Python, as my time is valuable (somewhat). I found jsyang/gearvr-controller-webbluetooth on GitHub, which had already reverse-engineered most of the Bluetooth stuff, but in WebBluetooth. I basically copied the important bits, used bleak for BLE and evdev for input emulation, and off I went.

Digging through the UUIDs, I spotted something awesome: the custom service UUID, when you convert it to ASCII, literally spells “Oculus Threemote”. Someone at Oculus and Samsung had a sense of humor, they could have just generated a random UUID and go with it. Remember to add those things to your code, you can make someone smile, sometimes.

Python Init Sequence showing the "Threemote" UUIDs and the essential byte commands

There were also some initialization commands in the original code that were commented out with “optional.” Optional my ass. Without them, the sensors stay completely dead. Controller won’t talk to me no more, no matter what I do.


After a bit of tinkering, it actually worked. I had a mouse. I had button mapping. I even implemented a rolling average for anti-drift because the gyro on this thing is… let’s say “optimistic”. I have quickly dumped it for just a touchpad.

Here would be a screenshot/video of me actually using it and showing logs buuuuut at the time of writing, controller is in uhhhh… peculiar state.

Now, let’s use it on Android

That one sentence turned this weekend project into multi-week endeavor (and we are just starting!).

Kotling Multiplatform is fun!

What do you do when you want to make an Android app? Naturally, use Kotlin Multiplatform (KMP) or Java. I’ve decided I hate Java, I hate Kotlin, and I especially hate the entire ecosystem. It never works how I want it to. But writing in Kotlin is still better than Java.

Gradle

I spent something like two hours reading Gradle docs just to figure out how to create a multi-package project (core, linux, android, etc.). Then I spent even more time wondering why my core library wasn’t working, only to realize KMP looks for files in commonMain instead of main.

Why? Because reasons.

Well, one can say it’s an skill issue, and it is probably. That doesn’t mean it should be this hard and time consuming to just pick up this framework or whatever they want to call it.

Kable and Rust

I used Kable for the Bluetooth stack. On Linux, Kable uses btleplug (written in Rust) under the hood. This led to one of the most baffling things I have seen in a while: a UniFFI contract version mismatch causing a Rust panic.

Imagine trying to debug a Kotlin app and getting a Rust backtrace because a library’s internal FFI binding decided to self-destruct. It required me to clean the entire project, manually, because Gradle commands could only do so much, and it fixed itself? The Web said nothing about this error, other than some Firefox source code and mozilla bug reports or something.

The Discord screenshot with "Eugeniusz" and "Krystian" and the giant yellow "Staring" emoji

Me

Pondering on a cold Stone

I finally got the Linux version to “work” (i.e., it didn’t crash on start). I could see the device. I could connect. I could even see the battery level and device info characteristics.

But the actual data characteristic? The one that carries the touchpad coordinates and sensor data?

Nuh-uh.

The "Silent" Kotlin Flow Subscription that promised data but delivered nothing

I tried everything. 1000ms delays. 2000ms delays. Every Flow operator known to man (onStart, onEach, catch). The write commands succeeded, but the notifications never came. btleplug claimed it was subscribed, but the controller was ghosting me.

Maybe it was a race condition. Maybe it was the BlueZ D-Bus API being “helpful.” Or maybe it was time to do something else?


After pondering about what to do next, the realization hit me: even if I fixed the Linux BLE stack in Kable, making this work on Android would require Accessibility Services to emulate a controller, which is its own kind of hell I didn’t want to step into.

I’ve reached the limit of my patience. I did manage to submit a PR to Kable (#1079), which fixed one of the issues I had, so at least my suffering wasn’t entirely for nothing.

Current Status: “Okay, fuck it. I won’t get anywhere like that.”

I’ll be back.

There will be more of it ;)

comments powered by Disqus