fix(gamepad): derive Android type_hint from device name (#270)#31
Conversation
The sokol-Android gamepad source already received the real `InputDevice.getName()` via the JNI glue and set `ev.setName()` / `ev.source_class`, but it never set `ev.type_hint`, so the `gamepad_connected` payload carried `unknown` and the HUD showed `[unknown]` where the desktop raylib path showed `[xbox]`. Add a shared, name-only `typeHintFromName` classifier to the gamepad contract (`gamepad.zig`), re-export it (plus `TypeHint`) from `gamepad_source/root.zig`, and use it in `android.zig`'s `labelle_android_on_device_added` to derive the vendor family from the buffered name. This matches the desktop (raylib) and web (wasm) name-only classification and is the canonical shared helper future backends should reuse instead of duplicating. Android-only emission stays gated behind the existing `is_android` checks; the classifier itself is platform-agnostic and host-tested. Refs: labelle-assembler#270
PR SummaryLow Risk Overview The PR adds Reviewed by Cursor Bugbot for commit 3a7300f. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
Assembler-side companion: labelle-toolkit/labelle-assembler#272 (gamepad-android demo |
There was a problem hiding this comment.
Code Review
This pull request introduces a shared name-based gamepad classifier (typeHintFromName) to derive the vendor family (TypeHint) from human-readable device names when stable USB vendor IDs are unavailable, specifically integrating it into the Android gamepad source. The review feedback highlights a potential misclassification bug where generic terms like "wireless controller" (checked in the PlayStation block) could override more specific brand keywords like "Nintendo" or "Switch" if the PlayStation checks are performed first. It is recommended to reorder the classification checks and add a corresponding test case to ensure "Nintendo Switch Wireless Controller" is correctly classified.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| pub fn typeHintFromName(name: []const u8) TypeHint { | ||
| if (containsIgnoreCase(name, "xbox") or | ||
| containsIgnoreCase(name, "microsoft")) return .xbox; | ||
| if (containsIgnoreCase(name, "playstation") or | ||
| containsIgnoreCase(name, "dualsense") or | ||
| containsIgnoreCase(name, "dualshock") or | ||
| containsIgnoreCase(name, "sony") or | ||
| containsIgnoreCase(name, "wireless controller")) return .playstation; | ||
| if (containsIgnoreCase(name, "nintendo") or | ||
| containsIgnoreCase(name, "switch") or | ||
| containsIgnoreCase(name, "joy-con") or | ||
| containsIgnoreCase(name, "pro controller")) return .nintendo; | ||
| if (name.len > 0) return .generic; | ||
| return .unknown; | ||
| } |
There was a problem hiding this comment.
Because "wireless controller" is a very generic term that matches many controllers (such as a "Nintendo Switch Wireless Controller"), checking for it in the PlayStation block before checking for Nintendo terms will cause Nintendo wireless controllers to be incorrectly classified as .playstation.
Swapping the order of the Nintendo and PlayStation checks resolves this, ensuring that specific Nintendo keywords like "nintendo" or "switch" are matched first, while still allowing the generic "wireless controller" fallback (common for Sony DualShock controllers on macOS/iOS) to correctly map to .playstation.
pub fn typeHintFromName(name: []const u8) TypeHint {
if (containsIgnoreCase(name, "xbox") or
containsIgnoreCase(name, "microsoft")) return .xbox;
if (containsIgnoreCase(name, "nintendo") or
containsIgnoreCase(name, "switch") or
containsIgnoreCase(name, "joy-con") or
containsIgnoreCase(name, "pro controller")) return .nintendo;
if (containsIgnoreCase(name, "playstation") or
containsIgnoreCase(name, "dualsense") or
containsIgnoreCase(name, "dualshock") or
containsIgnoreCase(name, "sony") or
containsIgnoreCase(name, "wireless controller")) return .playstation;
if (name.len > 0) return .generic;
return .unknown;
}
| test "typeHintFromName classifies known vendor families (name-only path)" { | ||
| try std.testing.expectEqual(TypeHint.xbox, typeHintFromName("Xbox Wireless Controller")); | ||
| try std.testing.expectEqual(TypeHint.xbox, typeHintFromName("XBOX 360 For Windows")); | ||
| try std.testing.expectEqual(TypeHint.xbox, typeHintFromName("Microsoft X-Box pad")); | ||
| try std.testing.expectEqual(TypeHint.playstation, typeHintFromName("Sony DualSense Wireless Controller")); | ||
| try std.testing.expectEqual(TypeHint.playstation, typeHintFromName("PLAYSTATION(R)3 Controller")); | ||
| try std.testing.expectEqual(TypeHint.playstation, typeHintFromName("Wireless Controller")); | ||
| try std.testing.expectEqual(TypeHint.nintendo, typeHintFromName("Nintendo Switch Pro Controller")); | ||
| try std.testing.expectEqual(TypeHint.nintendo, typeHintFromName("Joy-Con (L)")); | ||
| try std.testing.expectEqual(TypeHint.generic, typeHintFromName("Generic USB Joystick")); | ||
| try std.testing.expectEqual(TypeHint.unknown, typeHintFromName("")); | ||
| } |
There was a problem hiding this comment.
Add a test case for "Nintendo Switch Wireless Controller" to ensure that controllers containing both a specific brand keyword (like "Switch") and the generic "Wireless Controller" phrase are correctly classified under their specific brand rather than falling back to PlayStation.
test "typeHintFromName classifies known vendor families (name-only path)" {
try std.testing.expectEqual(TypeHint.xbox, typeHintFromName("Xbox Wireless Controller"));
try std.testing.expectEqual(TypeHint.xbox, typeHintFromName("XBOX 360 For Windows"));
try std.testing.expectEqual(TypeHint.xbox, typeHintFromName("Microsoft X-Box pad"));
try std.testing.expectEqual(TypeHint.playstation, typeHintFromName("Sony DualSense Wireless Controller"));
try std.testing.expectEqual(TypeHint.playstation, typeHintFromName("PLAYSTATION(R)3 Controller"));
try std.testing.expectEqual(TypeHint.playstation, typeHintFromName("Wireless Controller"));
try std.testing.expectEqual(TypeHint.nintendo, typeHintFromName("Nintendo Switch Pro Controller"));
try std.testing.expectEqual(TypeHint.nintendo, typeHintFromName("Nintendo Switch Wireless Controller"));
try std.testing.expectEqual(TypeHint.nintendo, typeHintFromName("Joy-Con (L)"));
try std.testing.expectEqual(TypeHint.generic, typeHintFromName("Generic USB Joystick"));
try std.testing.expectEqual(TypeHint.unknown, typeHintFromName(""));
}
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3a7300f. Configure here.
| containsIgnoreCase(name, "dualsense") or | ||
| containsIgnoreCase(name, "dualshock") or | ||
| containsIgnoreCase(name, "sony") or | ||
| containsIgnoreCase(name, "wireless controller")) return .playstation; |
There was a problem hiding this comment.
X-Box wireless misclassified PlayStation
Medium Severity
In typeHintFromName, the Xbox branch only matches the contiguous substring xbox, so names like X-Box Wireless Controller skip Xbox and Microsoft checks. The PlayStation branch then matches the wireless controller substring and returns .playstation, so some Xbox pads get PlayStation glyphs instead of Xbox on Android.
Reviewed by Cursor Bugbot for commit 3a7300f. Configure here.
…32) typeHintFromName checked the PlayStation block (which treats the generic 'wireless controller' as PS) before the Nintendo block, so a 'Nintendo Switch Wireless Controller' misclassified as playstation. Move the Nintendo brand-keyword check ahead of the generic PS fallback + add a regression test. (Re-do of a #31-review fix that was lost when its worktree was detached and the push silently no-op'd.)


Problem
On sokol-Android the
gamepad_connectedevent carriedtype_hint = .unknown, so the gamepad-android HUD showed[unknown]while the desktop raylib path showed[xbox]for the same controller (labelle-assembler#270).Tracing the path: the JNI glue (
backends/sokol/src/android_gamepad_jni.c) resolvesInputDevice.getName()and passes it to core'slabelle_android_on_device_added(device_id, sources, name_ptr, name_len, descriptor_ptr, descriptor_len). Core already calledev.setName(name)andev.source_class = classifySources(sources)— but never setev.type_hint. So the name reached core fine; only the type classification was missing.Fix
typeHintFromNameclassifier to the gamepad contract (gamepad.zig) — case-insensitive substring match for xbox / playstation / nintendo,.genericfor any other non-empty name,.unknownfor empty. This is the canonical version of the helper currently duplicated in the raylib backend (typeHintFromName) and the wasm source (typeHintFromId).TypeHint) fromgamepad_source/root.zig.android.zig'slabelle_android_on_device_added, setev.type_hint = source.typeHintFromName(ev.nameSlice()). Uses the buffered name so truncation is honored. Stays behind the existingis_androidgating.Tests
typeHintFromNamehost tests ingamepad.zig(xbox/ps/nintendo/generic/unknown).android.ziglocking the connect-event wiring (setNamethentypeHintFromName(nameSlice())→.xbox).zig build test: 84/84 pass, and the build'scheck-platformsstep compile-checksgamepad_sourcefor aarch64-linux-android (success), aarch64-ios, x86_64-linux, wasm32. Also verified a directzig build-obj -target aarch64-linux-android src/root.zig.On-device verification on the test tablet is pending (handled by the main session).
Closes the core half of labelle-assembler#270. The assembler PR (gamepad-android demo
nameForby-id fix) depends on this change for the[xbox]type half; cross-referenced there.