Skip to content

Let the guide button LED follow the battery - #67

Draft
trexx wants to merge 1 commit into
masterfrom
feature/guide-led-battery
Draft

trexx wants to merge 1 commit into
masterfrom
feature/guide-led-battery

Conversation

@trexx

@trexx trexx commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Summary

"Battery level" as a fifth value of the Xbox button brightness setting. The guide LED is monochrome, so following the battery means brightness steps, taken from the presets so each is a level the user has already seen:

Battery LED
full Bright 0x2F
medium Normal 0x14
low Dim 0x0A
critical slow blink at Normal
no battery (cable-powered) Bright
before the first report Normal (the start value)
  • GuideButtonLed holds every LED constant and the ladder on the Java side, where it is tested; PreferenceConfiguration.getGuideButtonLedValue() returns it. The native side only applies what it is handed and refuses a pattern outside enum LedMode, which is what catches the Java constants drifting from gip.h.
  • Native: Controller::setLed() stores the command and sends it once the device has started; startDevice() applies whatever is stored; both build the frame through one applyLed(). ledBrightness loses its const (the comment that justified it said nothing re-sends the command, which is no longer true) and gains a pattern, both std::atomic against the startThread fallback. New JNI entry setLedNative (JII)Z.

Fixed on the way: on the cabled path, a status arriving before the Java object existed armed the driver's change filter with nobody listening, so Java heard nothing until the battery actually moved. The filter now stays unarmed until a report is delivered; MS-GIPUSB 2.2.9 has the pad repeat status every second for its first ten seconds.

Split out of #66, which also carried an on-screen battery label; that work is preserved on feature/controller-battery-label.

Latency and performance impact

None on any per-frame path. One GIP frame per level change, on the driver's read thread, deduplicated against charge-only status changes.

Applies to

GIP pads this app drives (Xbox wireless adapter, or cabled Xbox with the wired driver on), on both boxes. The setting already sits under the USB-driver dependency.

Test plan

  • ./gradlew testDebugUnitTest — 336 tests, 0 failures (GuideButtonLedTest new; PreferenceConfigurationTest guide LED cases extended)
  • ./gradlew assembleRelease lintRelease — both ABIs build; the 2 lint warnings are pre-existing (UsbDriverService, Profiler)
  • On device: HARDWARE_TESTING.md §31 — in particular whether a pad honours LED_BLINK_SLOW at all, which Table 42 leaves open

🤖 Generated with Claude Code

"Battery level" is a fifth value of the Xbox button brightness setting. The
guide LED is monochrome, so following the battery means brightness steps,
taken from the presets so each one is a level the user has already seen: full
Bright, medium Normal, low Dim - and a slow blink at critical, since a warning
has to look like one and a dimmer steady LED across a room does not. A pad
with no battery fitted, running off its cable, goes Bright.

GuideButtonLed holds every LED constant and the ladder on the Java side,
where it is tested; PreferenceConfiguration.getGuideButtonLedValue() now
returns it, with "battery" starting at Normal until the pad reports. The
native side only applies what it is handed: Controller::setLed() stores the
command and sends it once the device has started, startDevice() applies
whatever is stored, and both build the frame through one applyLed(), so a
brightness of zero means the same thing on every path. ledBrightness loses
its const - the comment that justified it said nothing re-sends the command,
which is no longer true - and gains a pattern, both std::atomic against the
startThread fallback. A pattern outside enum LedMode is refused, which is what
catches the Java constants drifting from gip.h.

Also fixed on the way: on the cabled path the read thread runs before the
Java object exists, and a status in that window armed the driver's change
filter with nobody listening, so Java heard nothing until the battery
actually moved. The filter now stays unarmed until a report is delivered;
MS-GIPUSB 2.2.9 has the pad repeat status every second for its first ten
seconds.

Latency: nothing on a per-frame path. One GIP frame per level change, on the
driver's read thread, deduplicated against charge-only status changes.

Tests: GuideButtonLedTest and the guide LED cases in
PreferenceConfigurationTest. HARDWARE_TESTING.md 31 lists what needs a device
- in particular whether a pad honours LED_BLINK_SLOW at all, which Table 42
leaves open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant