Restrict the brightness sink to one app, picked by name not typed

Confirmed the brightness command applies globally, not per-LED
(serverinfo showed exactly one adjustment object, "id": "default",
covering the whole string), so no LED-count configuration is needed
for this at all -- that question resolved itself once the mechanism
was actually inspected instead of assumed.

For "only react while Spotify is running": only one app can be in the
foreground on webOS at a time, so a "capture the current app" button
in this app's own UI can never work -- pressing it means this app is
foreground, not Spotify. The only workable UI is picking a target from
every *installed* app by name, regardless of what's currently running.

That needed a new native capability this service never had: calling
OUT to another Luna service, not just being called. Two additions:

  foreground_app.c   subscribes once, at startup, to
                      com.webos.applicationManager/getForegroundAppInfo
                      and keeps a thread-safe cache the audio thread can
                      read without a blocking Luna call
  service.c           new listApps method, bridging to
                      com.webos.applicationManager/listApps so the
                      frontend never has to call another service
                      directly -- same rule as everywhere else here

Until the subscription has delivered at least one reply, a restricted
sink treats the target app as inactive, not active -- reacting to
audio when the user explicitly restricted it to one app would be the
wrong failure mode. Verified end to end on the host: engine_smoke.c
opens the sink with a restriction set, confirms it reports itself
correctly inactive against the stub Luna bus (which always "fails" to
call out, exactly like a real host with no bus).

Needed real, linkable stub bodies for LSCall/LSCallOneReply/
LSCallCancel/LSMessageGetPayload/LSErrorInit/LSErrorFree
(test/stubs/luna-service2/lunaservice_stub.c) since foreground_app.c
is the first source file here that's actually linked into a host test
binary rather than only syntax-checked -- service.c/main.c's existing
stub declarations were never called, only compiled against. Confirmed
those really are the correct symbol names by cross-compiling clean
against the real webOS SDK's actual libluna-service2, not just the
stub.

Bumped to 1.0.5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Rene Kievits
2026-08-26 15:46:09 +02:00
co-authored by Claude Opus 5
parent e9f6c87d27
commit d3e4cb6410
20 changed files with 411 additions and 13 deletions
+15 -3
View File
@@ -204,12 +204,22 @@ static void test_status(engine_t* e)
check(json_len(json_get(levels, "bands")) == DSP_BANDS, "all bands reported");
const json_value_t* sinks = json_get(v, "sinks");
check(json_len(sinks) == 2, "two sinks reported");
check(json_len(sinks) == 3, "three sinks reported");
for (size_t i = 0; i < json_len(sinks); i++) {
const json_value_t* s = json_at(sinks, i);
char label[64];
snprintf(label, sizeof(label), "sink '%s' started cleanly", json_str(s, "id", "?"));
check(json_bool(s, "ok", false), label);
if (strcmp(json_str(s, "id", ""), "hyperhdrAdjust") == 0) {
// A host has no Luna bus (see lunaservice_stub.c), so the
// sink can never confirm the restricted app is foreground.
// The correct failure mode is inactive, not "assume yes".
check(strcmp(json_str(s, "restrictToApp", ""), "some.other.app") == 0,
"restriction target reported back");
check(json_bool(s, "restrictedAppActive", true) == false,
"restricted app correctly reported as not active (fail-closed)");
}
}
json_free(v);
}
@@ -224,9 +234,11 @@ int main(void)
char cfg_text[512];
snprintf(cfg_text, sizeof(cfg_text),
"{\"capture\":{\"backend\":\"tone\",\"rate\":48000,\"channels\":2},"
"\"sinks\":[\"tcp\",\"http\"],"
"\"sinks\":[\"tcp\",\"http\",\"hyperhdrAdjust\"],"
"\"tcp\":{\"port\":%d},"
"\"http\":{\"port\":%d}}",
"\"http\":{\"port\":%d},"
"\"hyperhdrAdjust\":{\"host\":\"127.0.0.1\",\"port\":19444,"
"\"restrictToApp\":\"some.other.app\"}}",
TCP_PORT, HTTP_PORT);
json_value_t* cfg = json_parse(cfg_text);