Files
2026-10-06 22:26:27 +02:00

197 lines
8.2 KiB
C

/* Luna API handlers. Included after the shared runtime context. */
#include "service_calibration.h"
static bool start_failure(void *h, void *m, const char *message) {
retry_update(&retries, boot_enabled() && geteuid() == 0, false, milliseconds());
char json[512];
snprintf(
json, sizeof(json),
"{\"returnValue\":false,\"errorText\":\"%s\",\"retryScheduled\":%s,\"retryBlocked\":%s}",
message, retries.due ? "true" : "false", retries.blocked ? "true" : "false");
return respond(h, m, json);
}
static bool start(void *h, void *m, void *ctx) {
(void)ctx;
const char *p = m ? payload(m) : NULL;
if (p && strcmp(p, "{}") != 0)
return respond(h, m,
"{\"returnValue\":false,\"errorText\":\"Start expects an empty object; use "
"settings to configure the receiver\"}");
if (m)
retry_reset(&retries);
if (piccap_owned)
return respond(h, m, "{\"returnValue\":true,\"running\":true,\"activeSource\":\"PicCap\"}");
if (capture_ctx.handle)
return respond(h, m, "{\"returnValue\":true,\"running\":true}");
if (!playback_json.lib) {
set_error("JSON-C is unavailable; cannot start receiver output");
return start_failure(h, m, "JSON-C is unavailable");
}
capture_ctx.frames = 0;
capture_ctx.revoked = 0;
last_error[0] = 0;
capture_ctx.diagnostic[0] = 0;
if (receiver_runtime.starting)
return respond(h, m, "{\"returnValue\":true,\"connecting\":true}");
receiver_runtime.clear_pending = false;
if (!output_begin(&receiver_runtime.output, &target, milliseconds())) {
set_error(receiver_runtime.output.error);
return start_failure(h, m, receiver_runtime.output.error);
}
receiver_runtime.starting = true;
return respond(h, m, "{\"returnValue\":true,\"connecting\":true}");
}
static bool initialize_capture(void *h, void *m) {
if (!enter_developer_mode()) {
stop_capture();
set_error("Developer initialization needs root. Elevate this own service through Homebrew "
"Channel.");
return start_failure(h, m,
"Root access is required for temporary LG developer initialization");
}
capture_ctx.handle = vt_init(policy);
if (!leave_developer_mode()) {
stop_capture();
retries.blocked = true;
set_error("Developer marker cleanup failed; manual intervention required");
return start_failure(h, m, "Developer marker cleanup failed");
}
if (!capture_ctx.handle) {
stop_capture();
set_error("LG refused histogram initialization");
return start_failure(
h, m,
"LG refused histogram initialization. Another capture source may hold VT_CAPTURE.");
}
if (vt_preset(capture_ctx.handle, 0) != 0) {
stop_capture();
set_error("LG histogram preset initialization failed");
return start_failure(h, m, "Histogram preset failed");
}
capture_ctx.preset = capture_ctx.letterbox_mode = capture_ctx.settling = 0;
capture_ctx.pending_letterbox = -1;
capture_ctx.next_border_probe = 0;
retries.due = 0;
retries.attempts = 0;
retries.quick_retries = 0;
if (capture_ctx.recovery_started) {
capture_ctx.last_recovery_ms = milliseconds() - capture_ctx.recovery_started;
capture_ctx.recovery_started = 0;
}
return respond(h, m, "{\"returnValue\":true,\"running\":true}");
}
static bool stop(void *h, void *m, void *ctx) {
(void)ctx;
calibration_ctx.until = 0;
retry_pause(&retries);
bool bridging = bridge_phase != BRIDGE_IDLE;
stream_close(milliseconds());
bridge_phase = BRIDGE_IDLE;
memset(&capture_ctx.continuity, 0, sizeof(capture_ctx.continuity));
stop_capture();
if (bridging && receiver_runtime.output.stage != OUTPUT_READY) {
output_begin(&receiver_runtime.output, &target, milliseconds());
receiver_runtime.clear_pending = true;
}
return respond(h, m, "{\"returnValue\":true,\"running\":false}");
}
static bool autostart(void *h, void *m, void *ctx) {
(void)ctx;
const char *p = m ? payload(m) : NULL;
bool enabled;
if (p && strcmp(p, "{\"enabled\":true}") == 0)
enabled = true;
else if (p && strcmp(p, "{\"enabled\":false}") == 0)
enabled = false;
else
return respond(h, m, "{\"returnValue\":false,\"errorText\":\"Expected enabled boolean\"}");
if (geteuid() != 0)
return respond(
h, m,
"{\"returnValue\":false,\"rootRequired\":true,\"errorText\":\"Root access is required "
"to change autostart. Select Enable Root Access in Overview.\"}");
if (!set_boot(enabled))
return respond(
h, m,
"{\"returnValue\":false,\"errorText\":\"Cannot update the HyperDRM boot hook. Check "
"the installed boot script and whether another file occupies the hook path.\"}");
if (enabled)
retry_reset(&retries);
retry_update(&retries, enabled, capture_ctx.handle != NULL, milliseconds());
return respond(h, m,
enabled ? "{\"returnValue\":true,\"autostart\":true}"
: "{\"returnValue\":true,\"autostart\":false}");
}
static bool restart_service(void *h, void *m, void *ctx) {
(void)ctx;
if (strcmp(payload(m), "{}"))
return respond(h, m,
"{\"returnValue\":false,\"errorText\":\"Restart expects an empty object\"}");
bool ok = respond(h, m, "{\"returnValue\":true,\"restarting\":true}");
quitting = 1;
return ok;
}
static bool handover(void *h, void *m, void *ctx) {
(void)ctx;
const char *p = payload(m);
bool enabled;
if (strcmp(p, "{\"enabled\":true}") == 0)
enabled = true;
else if (strcmp(p, "{\"enabled\":false}") == 0)
enabled = false;
else
return respond(h, m, "{\"returnValue\":false,\"errorText\":\"Expected enabled boolean\"}");
if (!playback_json.lib || geteuid() != 0)
return respond(
h, m, "{\"returnValue\":false,\"errorText\":\"Root service and JSON-C are required\"}");
if (enabled && !piccap_installed())
return respond(h, m, "{\"returnValue\":false,\"errorText\":\"PicCap is not installed\"}");
if (!handover_save(enabled))
return respond(
h, m,
"{\"returnValue\":false,\"errorText\":\"Could not save PicCap handover preference\"}");
handover_enabled = enabled;
protection = PLAYBACK_UNKNOWN;
clear_samples = 0;
clear_since = unknown_since = 0;
next_media_probe = 0;
return respond(h, m, "{\"returnValue\":true}");
}
static bool settings(void *h, void *m, void *ctx) {
(void)ctx;
TargetConfig next = target;
if (!playback_json.lib || !target_parse(&playback_json, payload(m), &next))
return respond(h, m,
"{\"returnValue\":false,\"errorText\":\"Enter a valid IPv4/IPv6 address, "
"integer port (1-65535), receiver and optional API token\"}");
if (!target_save(TARGET_PREF, &next))
return respond(
h, m, "{\"returnValue\":false,\"errorText\":\"Could not save receiver settings\"}");
bool active = capture_ctx.handle || receiver_runtime.starting || piccap_owned || retries.due;
stop_capture();
stream_close(milliseconds());
bridge_phase = BRIDGE_IDLE;
memset(&capture_ctx.continuity, 0, sizeof(capture_ctx.continuity));
test_close();
connection_test.stage = TEST_IDLE;
target = next;
last_error[0] = 0;
protection = PLAYBACK_UNKNOWN;
clear_samples = 0;
clear_since = unknown_since = 0;
if (active && !retries.paused && !retries.blocked)
retries.due = milliseconds();
return respond(h, m, "{\"returnValue\":true}");
}
static bool test_connection(void *h, void *m, void *ctx) {
(void)ctx;
if (strcmp(payload(m), "{}") || !playback_json.lib)
return respond(
h, m,
"{\"returnValue\":false,\"errorText\":\"Test requires an empty object and JSON-C\"}");
if (!test_begin(&target, milliseconds()))
return respond(
h, m, "{\"returnValue\":false,\"errorText\":\"A connection test is already running\"}");
return respond(h, m, "{\"returnValue\":true,\"testing\":true}");
}
#include "service_status.h"