RGB keyboard lamp mirror + the shipped controls reference
KEYBOARD LAMP MIRROR (L4KEYLIGHT). Ported from RP412, itself a port of vRIO's KeyboardLampMirror. Keys bound to a lamp address in bindings.txt glow with the panel palette through Windows Dynamic Lighting -- yellow for the map's side columns (0x10-0x1F), red for the rest -- flashing in step with the on-screen buttons (its LampLevel copy matches the FIXED BTLampBrightnessOf). Per-key boards light each bound key; zone-lit boards mirror the strongest lamp board-wide. All WinRT runs on a private worker thread: watcher, claim, and a 100 ms paint loop that repaints only on change. RP412's packing hazard does NOT transfer: it forces default struct packing there because its engine is /Zp1, which would break the WinRT ABI. BT411's BT_OPTS carries no /Zp, so only the dialect flags are needed -- /std:c++17 /permissive- per-file, since the project otherwise builds C++14 /permissive. The scalars-only interface is kept anyway so the isolation survives if packing is ever added. Wired in PadRIO: map built from bindings.keyBindings (ActionButton binds only, first-binding-wins per key), fed from PadRIO::SetLamp, stopped in the dtor (which hands the LEDs back to Windows). BT_KEYLIGHT=0 opts out; a machine without Dynamic Lighting logs once and stays dormant. Verified live: claimed this machine's 24-zone keyboard and mirrors 25 bound keys; BT_KEYLIGHT=0 and the pod profile produce zero keylight lines and run clean. SHIPPED CONTROLS REFERENCE. docs/CONTROLS.md carries the keyboard table plus the full 72-BUTTON POD MAP, grouped by the display each bank surrounds, with the coolant-valve detent warning (1-5-50-CLOSED, one press past max shuts the loop) and the jam/eject procedure. mkdist flattens it to ASCII as CONTROLS.txt at the zip root, the README's own idiom. players/README.txt gained pointers to it, to environ.ini, to -fit, and to the RGB mirror. KB: context/glass-cockpit.md and docs/GLASS_COCKPIT.md updated for the whole branch -- layout modes, L4RIOBANK and the under-glass rule, the letterbox fit and its ordering trap, player-tunable displays, the measured legend grid, the closed dead-button backlog, and this mirror. Verified: five-mode regression (surround/exploded/dock/pod/dev) boots and simulates with zero faults; mkdist writes a 5149-byte pure-ASCII CONTROLS.txt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include "l4glasswin.h"
|
||||
#include "l4ctrl.h"
|
||||
#include "l4joy.h"
|
||||
#include "l4keylight.h" // RGB keyboard lamp mirror (scalars-only interface)
|
||||
|
||||
#include <windows.h>
|
||||
#include <xinput.h>
|
||||
@@ -63,6 +64,17 @@ int
|
||||
// XInput normalization: thumbs to -1..1 past the stock deadzone, triggers
|
||||
// to 0..1 past the stock threshold.
|
||||
//
|
||||
//
|
||||
// The lamp mirror's log sink. It cannot use DEBUG_STREAM itself: its TU
|
||||
// takes no engine headers (l4keylight.h is scalars only), so it hands us
|
||||
// finished lines instead.
|
||||
//
|
||||
static void
|
||||
KeyLightLog(const char *line)
|
||||
{
|
||||
DEBUG_STREAM << "[keylight] " << (line ? line : "") << "\n" << std::flush;
|
||||
}
|
||||
|
||||
static float
|
||||
NormalizeThumb(int value, int dead_zone)
|
||||
{
|
||||
@@ -170,6 +182,67 @@ PadRIO::PadRIO():
|
||||
flipStickAxes =
|
||||
(getenv("L4PADFLIP") != NULL && *getenv("L4PADFLIP") != '0');
|
||||
|
||||
//
|
||||
// RGB KEYBOARD LAMP MIRROR (Windows Dynamic Lighting, l4keylight.h):
|
||||
// every key bound to a lamp ADDRESS glows with the panel palette --
|
||||
// yellow for the Secondary/Screen columns (0x10-0x1F), red for the rest,
|
||||
// exactly like the on-screen buttons. Keypad binds have no lamp, so
|
||||
// they are skipped; a key bound twice takes its FIRST binding.
|
||||
// BT_KEYLIGHT=0 opts out; a machine without Dynamic Lighting logs once
|
||||
// and stays dormant.
|
||||
//
|
||||
keyLightActive = False;
|
||||
{
|
||||
const char *gate = getenv("BT_KEYLIGHT");
|
||||
if (gate == NULL || atoi(gate) != 0)
|
||||
{
|
||||
static int lightKeys[192];
|
||||
static int lightAddresses[192];
|
||||
static unsigned char lightYellow[192];
|
||||
int count = 0;
|
||||
for (int k = 0; k < bindings.keyBindingCount && count < 192; ++k)
|
||||
{
|
||||
const PadBindingProfile::KeyBinding &binding = bindings.keyBindings[k];
|
||||
if (binding.action.kind != PadBindingProfile::ActionButton)
|
||||
{
|
||||
continue; // axes/keypads carry no lamp
|
||||
}
|
||||
int address = binding.action.address;
|
||||
if (address < 0 || address >= LampCount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Logical duplicate = False;
|
||||
for (int j = 0; j < count; ++j)
|
||||
{
|
||||
if (lightKeys[j] == binding.virtualKey)
|
||||
{
|
||||
duplicate = True; // first binding wins
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (duplicate)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
lightKeys[count] = binding.virtualKey;
|
||||
lightAddresses[count] = address;
|
||||
lightYellow[count] = (unsigned char)
|
||||
((address >= 0x10 && address <= 0x1F) ? 1 : 0);
|
||||
++count;
|
||||
}
|
||||
if (count > 0)
|
||||
{
|
||||
KeyLight_SetLogger(KeyLightLog);
|
||||
KeyLight_SetMap(lightKeys, lightAddresses, lightYellow, count);
|
||||
KeyLight_Start();
|
||||
keyLightActive = True;
|
||||
DEBUG_STREAM << "[keylight] mirroring " << count
|
||||
<< " bound key(s) onto RGB keyboards\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Never revision 0.0 -- some diagnostics print it; give the synthetic
|
||||
// board a recognizable version.
|
||||
@@ -199,6 +272,11 @@ PadRIO::PadRIO():
|
||||
|
||||
PadRIO::~PadRIO()
|
||||
{
|
||||
if (keyLightActive)
|
||||
{
|
||||
KeyLight_Stop(); // hands the LEDs back to Windows
|
||||
keyLightActive = False;
|
||||
}
|
||||
BTGlassPanels_Destroy(); // safe no-op if the glass windows were never created
|
||||
BTPadPanel_Destroy();
|
||||
if (activeInstance == this)
|
||||
@@ -1038,6 +1116,18 @@ void
|
||||
if (lampNumber >= 0 && lampNumber < LampCount)
|
||||
{
|
||||
lampState[lampNumber] = state;
|
||||
if (keyLightActive)
|
||||
{
|
||||
// The mirror keeps its own copy (its paint loop runs on a private
|
||||
// thread and must not reach into the device); the first 64
|
||||
// addresses are the whole lamp field.
|
||||
unsigned char bytes[64];
|
||||
for (int i = 0; i < 64; ++i)
|
||||
{
|
||||
bytes[i] = (unsigned char) lampState[i];
|
||||
}
|
||||
KeyLight_UpdateLamps(bytes, 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user