Skip to content

Commit 643da65

Browse files
committed
Add persistent startup source with tray indicator and web UI button
1 parent a8131e4 commit 643da65

12 files changed

Lines changed: 419 additions & 42 deletions

File tree

assets/webconfig/js/content_remote.js

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ $(document).ready(function () {
132132
if (prios.length === 0) {
133133
$('.sstbody').append(`<tr><td colspan="4" class="text-center text-muted">${$.i18n('remote_input_no_sources')}</td></tr>`);
134134
$('#auto_btn').empty();
135+
appendStartupCheckbox();
135136
return;
136137
}
137138

@@ -243,6 +244,45 @@ $(document).ready(function () {
243244
if ($(this).innerWidth() > maxWidth) maxWidth = $(this).innerWidth();
244245
});
245246
$('.btn_input_selection').css("min-width", maxWidth + "px");
247+
248+
appendStartupCheckbox();
249+
}
250+
251+
function appendStartupCheckbox() {
252+
if ($("#save_startup_source_btn").length) return;
253+
$('#auto_btn').after(
254+
'<div id="save_startup_source_wrap" style="margin-top:12px; clear:both;">' +
255+
'<button id="save_startup_source_btn" type="button" class="btn btn-default btn-sm" style="width:100%;">' +
256+
'<span id="save_startup_source_icon" class="glyphicon glyphicon-unchecked" style="margin-right:6px;"></span>' +
257+
'<span id="save_startup_source_label">Use active source as startup source</span>' +
258+
'</button>' +
259+
'</div>'
260+
);
261+
$("#save_startup_source_btn").on("click", function () {
262+
if ($(this).hasClass("btn-success")) {
263+
sendToHyperion("startupsource", "set", { data: {} });
264+
$(this).removeClass("btn-success").addClass("btn-default");
265+
$("#save_startup_source_icon").removeClass("glyphicon-check").addClass("glyphicon-unchecked");
266+
$("#save_startup_source_label").text("Use active source as startup source");
267+
} else {
268+
saveStartupSource();
269+
}
270+
});
271+
applyStartupSource();
272+
}
273+
274+
function setStartupSourceBtnActive(active) {
275+
if ($("#save_startup_source_btn").length) {
276+
if (active) {
277+
$("#save_startup_source_btn").removeClass("btn-default").addClass("btn-success");
278+
$("#save_startup_source_icon").removeClass("glyphicon-unchecked").addClass("glyphicon-check");
279+
$("#save_startup_source_label").text("Disable custom source as startup");
280+
} else {
281+
$("#save_startup_source_btn").removeClass("btn-success").addClass("btn-default");
282+
$("#save_startup_source_icon").removeClass("glyphicon-check").addClass("glyphicon-unchecked");
283+
$("#save_startup_source_label").text("Use active source as startup source");
284+
}
285+
}
246286
}
247287

248288

@@ -414,6 +454,44 @@ $(document).ready(function () {
414454
setupEventListeners();
415455
}
416456

457+
// Save current source as startup source
458+
function saveStartupSource() {
459+
const prios = window.serverInfo.priorities;
460+
const active = prios.find(p => p.active && ["COLOR", "EFFECT", "IMAGE"].includes(p.componentId));
461+
if (!active) {
462+
sendToHyperion("startupsource", "set", { data: {} });
463+
setStartupSourceBtnActive(false);
464+
return;
465+
}
466+
const src = {
467+
componentId: active.componentId,
468+
duration_ms: active.duration_ms || 0
469+
};
470+
if (active.componentId === "COLOR") {
471+
src.color = active.value.RGB;
472+
} else if (active.componentId === "EFFECT") {
473+
src.effectName = active.owner;
474+
} else if (active.componentId === "IMAGE") {
475+
src.imageData = lastImgData;
476+
src.imageName = lastFileName;
477+
}
478+
sendToHyperion("startupsource", "set", { data: src });
479+
setStartupSourceBtnActive(true);
480+
}
481+
482+
// Query server for startup source state
483+
function applyStartupSource() {
484+
sendToHyperion("startupsource", "get", {});
485+
$(window.hyperion).one("cmd-startupsource", function (event) {
486+
const data = event.response.info;
487+
if (data && !$.isEmptyObject(data)) {
488+
setStartupSourceBtnActive(true);
489+
} else {
490+
setStartupSourceBtnActive(false);
491+
}
492+
});
493+
}
494+
417495
// Setup Event Listeners for Controls
418496
function setupEventListeners() {
419497
$("#reset_color").off().on("click", resetColor);
@@ -491,15 +569,32 @@ $(document).ready(function () {
491569
}
492570

493571
// Interval Updates and Event Handlers
572+
let startupApplied = false;
573+
494574
function setupEventListenersForUpdates() {
495575
$(window.hyperion).on('components-updated', (e, comp) => updateComponent(comp));
496576

577+
// Must register cmd-priorities-update BEFORE forceFirstUpdate() sends serverinfo
497578
$(window.hyperion).on("cmd-priorities-update", (event) => {
498579
window.serverInfo.priorities = event.response.data.priorities;
499580
window.serverInfo.priorities_autoselect = event.response.data.priorities_autoselect;
500581
updateInputSelect();
582+
if (!startupApplied && getStorage('startupSource')) {
583+
startupApplied = true;
584+
applyStartupSource();
585+
}
586+
});
587+
588+
// Reset flag on reconnect so apply re-runs
589+
$(window.hyperion).on("open", function () {
590+
startupApplied = false;
501591
});
502592

593+
// If ws already open when listeners are set up, allow apply
594+
if (globalThis.websocket && globalThis.websocket.readyState === WebSocket.OPEN) {
595+
startupApplied = false;
596+
}
597+
503598
$(window.hyperion).on("cmd-imageToLedMapping-update", (event) => {
504599
window.serverInfo.imageToLedMappingType = event.response.data.imageToLedMappingType;
505600
updateLedMapping();
@@ -530,8 +625,11 @@ $(document).ready(function () {
530625
// Initialize everything
531626
function init() {
532627
initColorPickerAndEffects();
533-
forceFirstUpdate();
534628
setupEventListenersForUpdates();
629+
forceFirstUpdate();
630+
631+
// Query server for startup source state
632+
setTimeout(function () { applyStartupSource(); }, 500);
535633
}
536634

537635
init();

include/api/JsonAPI.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,12 @@ private slots:
302302
///
303303
void handleSystemCommand(const QJsonObject &message, const JsonApiCommand& cmd);
304304

305+
/// Handle an incoming JSON message to get/set startup source
306+
///
307+
/// @param message the incoming message
308+
///
309+
void handleStartupSourceCommand(const QJsonObject &message, const JsonApiCommand& cmd);
310+
305311
/// Handle an incoming data request message
306312
///
307313
/// @param message the incoming message

include/api/JsonApiCommand.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class Command {
3333
SourceSelect,
3434
SysInfo,
3535
System,
36+
StartupSource,
3637
Temperature,
3738
Transform,
3839
VideoMode
@@ -67,6 +68,7 @@ class Command {
6768
case Transform: return "transform";
6869
case VideoMode: return "videomode";
6970
case Service: return "service";
71+
case StartupSource: return "startupsource";
7072
default: return "unknown";
7173
}
7274
}
@@ -113,6 +115,7 @@ class SubCommand {
113115
Resume,
114116
SaveName,
115117
SetConfig,
118+
SetStartupSource,
116119
Start,
117120
StartInstance,
118121
Stop,
@@ -165,6 +168,7 @@ class SubCommand {
165168
case Resume: return "resume";
166169
case SaveName: return "saveName";
167170
case SetConfig: return "setconfig";
171+
case SetStartupSource: return "set";
168172
case Start: return "start";
169173
case StartInstance: return "startInstance";
170174
case Stop: return "stop";
@@ -348,6 +352,9 @@ class ApiCommandRegister {
348352
{ {"serverinfo", "getSubscriptions"}, { Command::ServerInfo, SubCommand::GetSubscriptions, Authorization::Yes, InstanceCmd::No_or_Single, InstanceCmd::MustRun_Yes, NoListenerCmd::No } },
349353
{ {"serverinfo", "getSubscriptionCommands"}, { Command::ServerInfo, SubCommand::GetSubscriptionCommands, Authorization::No, InstanceCmd::No, InstanceCmd::MustRun_No, NoListenerCmd::No } },
350354
{ {"service", "discover"}, { Command::Service, SubCommand::Discover, Authorization::Yes, InstanceCmd::No, InstanceCmd::MustRun_No, NoListenerCmd::Yes } },
355+
{ {"startupsource", "get"}, { Command::StartupSource, SubCommand::Empty, Authorization::Yes, InstanceCmd::No, InstanceCmd::MustRun_No, NoListenerCmd::Yes } },
356+
{ {"startupsource", ""}, { Command::StartupSource, SubCommand::Empty, Authorization::Yes, InstanceCmd::No, InstanceCmd::MustRun_No, NoListenerCmd::Yes } },
357+
{ {"startupsource", "set"}, { Command::StartupSource, SubCommand::SetStartupSource, Authorization::Yes, InstanceCmd::No, InstanceCmd::MustRun_No, NoListenerCmd::Yes } },
351358
{ {"sourceselect", ""}, { Command::SourceSelect, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, InstanceCmd::MustRun_Yes, NoListenerCmd::Yes } },
352359
{ {"sysinfo", ""}, { Command::SysInfo, SubCommand::Empty, Authorization::Yes, InstanceCmd::No, InstanceCmd::MustRun_No, NoListenerCmd::Yes } },
353360
{ {"system", "restart"}, { Command::System, SubCommand::Restart, Authorization::Yes, InstanceCmd::No, InstanceCmd::MustRun_No, NoListenerCmd::Yes } },

include/utils/settings.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace settings {
3333
OSEVENTS,
3434
CECEVENTS,
3535
SCHEDEVENTS,
36+
STARTUPSOURCE,
3637
INVALID
3738
};
3839

@@ -70,6 +71,7 @@ namespace settings {
7071
case OSEVENTS: return "osEvents";
7172
case CECEVENTS: return "cecEvents";
7273
case SCHEDEVENTS: return "schedEvents";
74+
case STARTUPSOURCE: return "startupSource";
7375
default: return "invalid";
7476
}
7577
}
@@ -104,8 +106,9 @@ namespace settings {
104106
if (type == "flatbufServer") return FLATBUFSERVER;
105107
if (type == "protoServer") return PROTOSERVER;
106108
if (type == "osEvents") return OSEVENTS;
107-
if (type == "cecEvents") return CECEVENTS;
109+
if (type == "cecEvents") return CECEVENTS;
108110
if (type == "schedEvents") return SCHEDEVENTS;
111+
if (type == "startupSource") return STARTUPSOURCE;
109112
return INVALID;
110113
}
111114
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"type":"object",
3+
"required":true,
4+
"properties":{
5+
"command": {
6+
"type" : "string",
7+
"required" : true,
8+
"enum" : ["startupsource"]
9+
},
10+
"tan" : {
11+
"type" : "integer"
12+
},
13+
"subcommand": {
14+
"type" : "string",
15+
"required" : true,
16+
"enum" : ["get", "set"]
17+
},
18+
"data": {
19+
"type" : "object",
20+
"required" : false
21+
}
22+
},
23+
"additionalProperties": false
24+
}

libsrc/api/JSONRPC_schema/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"command": {
66
"type" : "string",
77
"required" : true,
8-
"enum": [ "color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "instance-data", "leddevice", "inputsource", "service", "system", "transform", "correction", "temperature" ]
8+
"enum": [ "color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "instance-data", "leddevice", "inputsource", "service", "system", "transform", "correction", "temperature", "startupsource" ]
99
}
1010
}
1111
}

libsrc/api/JSONRPC_schemas.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<file alias="schema-inputsource">JSONRPC_schema/schema-inputsource.json</file>
2626
<file alias="schema-service">JSONRPC_schema/schema-service.json</file>
2727
<file alias="schema-system">JSONRPC_schema/schema-system.json</file>
28+
<file alias="schema-startupsource">JSONRPC_schema/schema-startupsource.json</file>
2829
<!-- The following schemas are derecated but used to ensure backward compatibility with hyperion Classic remote control-->
2930
<file alias="schema-transform">JSONRPC_schema/schema-hyperion-classic.json</file>
3031
<file alias="schema-correction">JSONRPC_schema/schema-hyperion-classic.json</file>

libsrc/api/JsonAPI.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,9 @@ void JsonAPI::handleCommand(const JsonApiCommand& cmd, const QJsonObject &messag
526526
case Command::ClearAll:
527527
handleClearallCommand(message, cmd);
528528
break;
529+
case Command::StartupSource:
530+
handleStartupSourceCommand(message, cmd);
531+
break;
529532
case Command::InstanceData:
530533
handleInstanceDataCommand(message, cmd);
531534
break;
@@ -861,6 +864,45 @@ void JsonAPI::handleClearallCommand(const QJsonObject &message, const JsonApiCom
861864
sendSuccessReply(cmd);
862865
}
863866

867+
void JsonAPI::handleStartupSourceCommand(const QJsonObject &message, const JsonApiCommand& cmd)
868+
{
869+
if (cmd.subCommand == SubCommand::SetStartupSource)
870+
{
871+
QJsonObject data = message["data"].toObject();
872+
Info(_log, "Saving startup source: %s", QSTRING_CSTR(QString::fromUtf8(JsonUtils::toCompact(data))));
873+
QJsonObject config;
874+
config["startupSource"] = data;
875+
auto hyperion = _hyperionWeak.toStrongRef();
876+
if (!hyperion.isNull())
877+
{
878+
hyperion->saveSettings(config);
879+
}
880+
else
881+
{
882+
SettingsManager mgr(0);
883+
mgr.saveSettings(config);
884+
}
885+
sendSuccessReply(cmd);
886+
}
887+
else
888+
{
889+
// Get stored startup source
890+
QJsonObject data;
891+
auto hyperion = _hyperionWeak.toStrongRef();
892+
if (!hyperion.isNull())
893+
{
894+
data = hyperion->getSetting(settings::STARTUPSOURCE).object();
895+
}
896+
else
897+
{
898+
SettingsManager mgr(0);
899+
data = mgr.getSetting(settings::STARTUPSOURCE).object();
900+
}
901+
Info(_log, "Read startup source: %s", QSTRING_CSTR(QString::fromUtf8(JsonUtils::toCompact(data))));
902+
sendSuccessDataReply(QJsonValue(data), cmd);
903+
}
904+
}
905+
864906
void JsonAPI::handleAdjustmentCommand(const QJsonObject &message, const JsonApiCommand& cmd)
865907
{
866908
QSharedPointer<Hyperion> hyperion = _hyperionWeak.toStrongRef();

0 commit comments

Comments
 (0)