mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
* Refactor to fix #1671 * Add GUI/NonGUI mode to info page * Do not show lock config, if in non-UI mode * Updae Changelog * Correct includes * Ensure key member initialization - RGB Channels * Ensure key member initialization - WebServer * Update RGBChannels * Fix initialization order * Fix key when inserting new logger in LoggerMap, Prepare logBuffer-JSON snapshot view in LoggerManager, Increase buffered loglines to 500 * Fix Memory leak in GrabberWrapper * Fix Memory leak in BlackBorderProcessor * Fix Memory leak in BlackBorderProcessor * use ninja generator under macos * Fix BGEffectHandler destruction * Fix Mdns code * Clear list after applying qDeleteAll * Fix deletion of CecHandler * Fix memory leak caused by wrong buffer allocation * Remove extra pixel consistently * Change mDNS to Qt SmartPointers * Correct removal * Fix usage of _width/_height (they are the output resolution, not the screen resolution) That avoids unnecessary resizing of the output image with every transferFrame call * Move main non Thread Objects to Smart Pointers * Revert "Move main non Thread Objects to Smart Pointers" This reverts commit 26102ca963982e2fbc4ffb8d4db6139f0128a3cc. * Add missing deletes * Revert MdnsBrowser chnage * Revert MdnsBrowser change * Fix memory leaks related standalone grabber * Address CodeQL finding * delete pointer OsxFrameGrabber --------- Co-authored-by: Paulchen-Panther <16664240+Paulchen-Panther@users.noreply.github.com>
417 lines
20 KiB
C++
417 lines
20 KiB
C++
// stl includes
|
|
#include <clocale>
|
|
#include <initializer_list>
|
|
#include <limits>
|
|
#include <iostream>
|
|
#include <stdlib.h>
|
|
|
|
// Qt includes
|
|
#include <QCoreApplication>
|
|
#include <QLocale>
|
|
|
|
#include <utils/Logger.h>
|
|
|
|
#include "HyperionConfig.h"
|
|
#include <commandline/Parser.h>
|
|
|
|
#ifdef ENABLE_MDNS
|
|
// mDNS discover
|
|
#include <mdns/MdnsBrowser.h>
|
|
#include <mdns/MdnsServiceRegister.h>
|
|
#else
|
|
// ssdp discover
|
|
#include <ssdp/SSDPDiscover.h>
|
|
#endif
|
|
#include <utils/NetUtils.h>
|
|
|
|
// hyperion-remote include
|
|
#include "JsonConnection.h"
|
|
|
|
#include <utils/DefaultSignalHandler.h>
|
|
|
|
using namespace commandline;
|
|
|
|
/// Count the number of true values in a list of booleans
|
|
int count(std::initializer_list<bool> values)
|
|
{
|
|
int count = 0;
|
|
for (bool value : values) {
|
|
if (value)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void showHelp(Option & option){
|
|
QString shortOption;
|
|
QString longOption = QString("--%1").arg(option.names().constLast());
|
|
|
|
if(option.names().size() == 2){
|
|
shortOption = QString("-%1").arg(option.names().constFirst());
|
|
}
|
|
|
|
qWarning() << qPrintable(QString("\t%1\t%2\t%3").arg(shortOption, longOption, option.description()));
|
|
}
|
|
|
|
int getInstaneIdbyName(const QJsonObject & reply, const QString & name){
|
|
if(reply.contains("instance")){
|
|
const QJsonArray list = reply.value("instance").toArray();
|
|
|
|
for ( const auto &entry : list ) {
|
|
const QJsonObject obj = entry.toObject();
|
|
if(obj["friendly_name"] == name && obj["running"].toBool())
|
|
{
|
|
return obj["instance"].toInt();
|
|
}
|
|
}
|
|
}
|
|
std::cout << "Can't find a running instance with name '" << name.toStdString()<< "' at this Hyperion server, will use first instance" << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char * argv[])
|
|
{
|
|
Logger* log = Logger::getInstance("REMOTE");
|
|
Logger::setLogLevel(Logger::INFO);
|
|
|
|
std::cout
|
|
<< "hyperion-remote:" << std::endl
|
|
<< "\tVersion : " << HYPERION_VERSION << " (" << HYPERION_BUILD_ID << ")" << std::endl
|
|
<< "\tbuild time: " << __DATE__ << " " << __TIME__ << std::endl;
|
|
|
|
DefaultSignalHandler::install();
|
|
|
|
QCoreApplication app(argc, argv);
|
|
|
|
// force the locale
|
|
setlocale(LC_ALL, "C");
|
|
QLocale::setDefault(QLocale::c());
|
|
|
|
try
|
|
{
|
|
// create the option parser and initialize all parameters
|
|
Parser parser("Application to send a command to hyperion using the JSON interface");
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// art variable definition append art to Parser short-, long option description, optional default value //
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
Option & argAddress = parser.add<Option> ('a', "address" , "The hostname or IP-address (IPv4 or IPv6) of the hyperion server.\nDefault port: 19444.\nSample addresses:\nHost : hyperion.fritz.box\nIPv4 : 127.0.0.1:19444\nIPv6 : [2001:1:2:3:4:5:6:7]", "127.0.0.1");
|
|
Option & argToken = parser.add<Option> ('t', "token" , "If authorization tokens are required, this token is used");
|
|
Option & argInstance = parser.add<Option> ('I', "instance" , "Select a specific target instance by name for your command. By default it uses always the first instance");
|
|
IntOption & argPriority = parser.add<IntOption> ('p', "priority" , "Used to the provided priority channel (suggested 2-99) [default: %1]", "50");
|
|
IntOption & argDuration = parser.add<IntOption> ('d', "duration" , "Specify how long the LEDs should be switched on in milliseconds [default: infinity]");
|
|
ColorsOption & argColor = parser.add<ColorsOption> ('c', "color" , "Set all LEDs to a constant color (either RRGGBB hex getColors or a color name. The color may be repeated multiple time like: RRGGBBRRGGBB)");
|
|
ImageOption & argImage = parser.add<ImageOption> ('i', "image" , "Set the LEDs to the colors according to the given image file");
|
|
#if defined(ENABLE_EFFECTENGINE)
|
|
Option & argEffect = parser.add<Option> ('e', "effect" , "Enable the effect with the given name");
|
|
Option & argEffectFile = parser.add<Option> (0x0, "effectFile" , "Arguments to use in combination with --createEffect");
|
|
Option & argEffectArgs = parser.add<Option> (0x0, "effectArgs" , "Arguments to use in combination with the specified effect. Should be a JSON object string.", "");
|
|
Option & argCreateEffect = parser.add<Option> (0x0, "createEffect" , "Write a new JSON Effect configuration file.\nFirst parameter = Effect name.\nSecond parameter = Effect file (--effectFile).\nLast parameter = Effect arguments (--effectArgs.)", "");
|
|
Option & argDeleteEffect = parser.add<Option> (0x0, "deleteEffect" , "Delete a custom created JSON Effect configuration file.");
|
|
#endif
|
|
BooleanOption & argClear = parser.add<BooleanOption>('x', "clear" , "Clear data for the priority channel provided by the -p option");
|
|
BooleanOption & argClearAll = parser.add<BooleanOption>(0x0, "clearall" , "Clear data for all active priority channels");
|
|
Option & argEnableComponent = parser.add<Option> ('E', "enable" , "Enable the Component with the given name. Available Components are [SMOOTHING, BLACKBORDER, FORWARDER, BOBLIGHTSERVER, GRABBER, V4L, AUDIO, LEDDEVICE]");
|
|
Option & argDisableComponent = parser.add<Option> ('D', "disable" , "Disable the Component with the given name. Available Components are [SMOOTHING, BLACKBORDER, FORWARDER, BOBLIGHTSERVER, GRABBER, V4L, AUDIO, LEDDEVICE]");
|
|
Option & argId = parser.add<Option> ('q', "qualifier" , "Identifier(qualifier) of the adjustment to set");
|
|
IntOption & argBrightness = parser.add<IntOption> ('L', "brightness" , "Set the brightness gain of the LEDs");
|
|
IntOption & argBrightnessC = parser.add<IntOption> (0x0, "brightnessCompensation" , "Set the brightness compensation");
|
|
IntOption & argBacklightThreshold = parser.add<IntOption> ('n', "backlightThreshold" , "threshold for activating backlight (minimum brightness)");
|
|
IntOption & argBacklightColored = parser.add<IntOption> (0x0, "backlightColored" , "0 = white backlight; 1 = colored backlight");
|
|
DoubleOption & argGamma = parser.add<DoubleOption> ('g', "gamma" , "Set the overall gamma of the LEDs");
|
|
ColorOption & argRAdjust = parser.add<ColorOption> ('R', "redAdjustment" , "Set the adjustment of the red color (requires colors in hex format as RRGGBB)");
|
|
ColorOption & argGAdjust = parser.add<ColorOption> ('G', "greenAdjustment" , "Set the adjustment of the green color (requires colors in hex format as RRGGBB)");
|
|
ColorOption & argBAdjust = parser.add<ColorOption> ('B', "blueAdjustment" , "Set the adjustment of the blue color (requires colors in hex format as RRGGBB)");
|
|
ColorOption & argCAdjust = parser.add<ColorOption> ('C', "cyanAdjustment" , "Set the adjustment of the cyan color (requires colors in hex format as RRGGBB)");
|
|
ColorOption & argMAdjust = parser.add<ColorOption> ('M', "magentaAdjustment" , "Set the adjustment of the magenta color (requires colors in hex format as RRGGBB)");
|
|
ColorOption & argYAdjust = parser.add<ColorOption> ('Y', "yellowAdjustment" , "Set the adjustment of the yellow color (requires colors in hex format as RRGGBB)");
|
|
ColorOption & argWAdjust = parser.add<ColorOption> ('W', "whiteAdjustment" , "Set the adjustment of the white color (requires colors in hex format as RRGGBB)");
|
|
ColorOption & argbAdjust = parser.add<ColorOption> ('b', "blackAdjustment" , "Set the adjustment of the black color (requires colors in hex format as RRGGBB)");
|
|
Option & argMapping = parser.add<Option> ('m', "ledMapping" , "Set the method for image to led mapping valid values: multi color_mean, uni-color_mean");
|
|
Option & argVideoMode = parser.add<Option> ('V', "videoMode" , "Set the video mode valid values: 2D, 3DSBS, 3DTAB");
|
|
IntOption & argSource = parser.add<IntOption> (0x0, "sourceSelect" , "Set current active priority channel and deactivate auto source switching");
|
|
BooleanOption & argSourceAuto = parser.add<BooleanOption>(0x0, "sourceAutoSelect" , "Enables auto source, if disabled prio by manual selecting input source");
|
|
BooleanOption & argOff = parser.add<BooleanOption>(0x0, "off" , "Deactivates hyperion");
|
|
BooleanOption & argOn = parser.add<BooleanOption>(0x0, "on" , "Activates hyperion");
|
|
BooleanOption & argConfigGet = parser.add<BooleanOption>(0x0, "configGet" , "Print the current loaded Hyperion configuration file");
|
|
BooleanOption & argSchemaGet = parser.add<BooleanOption>(0x0, "schemaGet" , "Print the JSON schema for Hyperion configuration");
|
|
Option & argConfigSet = parser.add<Option> (0x0, "configSet" , "Write to the actual loaded configuration file. Should be a JSON object string.");
|
|
BooleanOption & argServerInfo = parser.add<BooleanOption>('l', "list" , "List server info and active effects with priority and duration");
|
|
BooleanOption & argSysInfo = parser.add<BooleanOption>('s', "sysinfo" , "Show system info");
|
|
BooleanOption & argSystemSuspend = parser.add<BooleanOption>(0x0, "suspend" , "Suspend Hyperion. Stop all instances and components");
|
|
BooleanOption & argSystemResume = parser.add<BooleanOption>(0x0, "resume" , "Resume Hyperion. Start all instances and components");
|
|
BooleanOption & argSystemToggleSuspend= parser.add<BooleanOption>(0x0, "toggleSuspend" , "Toggle between Suspend and Resume. First request will trigger suspend");
|
|
BooleanOption & argSystemIdle = parser.add<BooleanOption>(0x0, "idle" , "Put Hyperion in Idle mode, i.e. all instances, components will be disabled besides the output processing (LED-devices, smoothing).");
|
|
BooleanOption & argSystemToggleIdle = parser.add<BooleanOption>(0x0, "toggleIdle" , "Toggle between Idle and Working mode. First request will trigger Idle mode");
|
|
BooleanOption & argSystemRestart = parser.add<BooleanOption>(0x0, "restart" , "Restart Hyperion");
|
|
|
|
BooleanOption & argPrint = parser.add<BooleanOption>(0x0, "print", "Print the JSON input and output messages on stdout");
|
|
BooleanOption & argDebug = parser.add<BooleanOption>(0x0, "debug", "Enable debug logging");
|
|
BooleanOption & argHelp = parser.add<BooleanOption>('h', "help", "Show this help message and exit");
|
|
|
|
// parse all _options
|
|
parser.process(app);
|
|
|
|
// check if debug logging is required
|
|
if (parser.isSet(argDebug))
|
|
{
|
|
Logger::setLogLevel(Logger::DEBUG);
|
|
}
|
|
|
|
// check if we need to display the usage. exit if we do.
|
|
if (parser.isSet(argHelp))
|
|
{
|
|
parser.showHelp(0);
|
|
}
|
|
|
|
// check if at least one of the available color transforms is set
|
|
bool colorAdjust = parser.isSet(argRAdjust) || parser.isSet(argGAdjust) || parser.isSet(argBAdjust) || parser.isSet(argCAdjust) || parser.isSet(argMAdjust)
|
|
|| parser.isSet(argYAdjust) || parser.isSet(argWAdjust) || parser.isSet(argbAdjust) || parser.isSet(argGamma)|| parser.isSet(argBrightness)|| parser.isSet(argBrightnessC)
|
|
|| parser.isSet(argBacklightThreshold) || parser.isSet(argBacklightColored);
|
|
|
|
// check that exactly one command was given
|
|
int commandCount = count({ parser.isSet(argColor), parser.isSet(argImage),
|
|
#if defined(ENABLE_EFFECTENGINE)
|
|
parser.isSet(argEffect), parser.isSet(argCreateEffect), parser.isSet(argDeleteEffect),
|
|
#endif
|
|
parser.isSet(argServerInfo), parser.isSet(argSysInfo),
|
|
parser.isSet(argSystemSuspend), parser.isSet(argSystemResume), parser.isSet(argSystemToggleSuspend),
|
|
parser.isSet(argSystemIdle), parser.isSet(argSystemToggleIdle),
|
|
parser.isSet(argSystemRestart),
|
|
parser.isSet(argClear), parser.isSet(argClearAll), parser.isSet(argEnableComponent), parser.isSet(argDisableComponent), colorAdjust,
|
|
parser.isSet(argSource), parser.isSet(argSourceAuto), parser.isSet(argOff), parser.isSet(argOn), parser.isSet(argConfigGet), parser.isSet(argSchemaGet), parser.isSet(argConfigSet),
|
|
parser.isSet(argMapping),parser.isSet(argVideoMode) });
|
|
if (commandCount != 1)
|
|
{
|
|
qWarning() << (commandCount == 0 ? "No command found." : "Multiple commands found.") << " Provide exactly one of the following options:";
|
|
showHelp(argColor);
|
|
showHelp(argImage);
|
|
#if defined(ENABLE_EFFECTENGINE)
|
|
showHelp(argEffect);
|
|
showHelp(argCreateEffect);
|
|
showHelp(argDeleteEffect);
|
|
#endif
|
|
showHelp(argServerInfo);
|
|
showHelp(argSysInfo);
|
|
showHelp(argSystemSuspend);
|
|
showHelp(argSystemResume);
|
|
showHelp(argSystemToggleSuspend);
|
|
showHelp(argSystemIdle);
|
|
showHelp(argSystemToggleIdle);
|
|
showHelp(argSystemRestart);
|
|
showHelp(argClear);
|
|
showHelp(argClearAll);
|
|
showHelp(argEnableComponent);
|
|
showHelp(argDisableComponent);
|
|
showHelp(argSource);
|
|
showHelp(argSourceAuto);
|
|
showHelp(argConfigGet);
|
|
showHelp(argVideoMode);
|
|
qWarning() << "or one or more of the available color modding operations:";
|
|
showHelp(argId);
|
|
showHelp(argBrightness);
|
|
showHelp(argBrightnessC);
|
|
showHelp(argBacklightThreshold);
|
|
showHelp(argBacklightColored);
|
|
showHelp(argGamma);
|
|
showHelp(argRAdjust);
|
|
showHelp(argGAdjust);
|
|
showHelp(argBAdjust);
|
|
showHelp(argCAdjust);
|
|
showHelp(argMAdjust);
|
|
showHelp(argYAdjust);
|
|
return 1;
|
|
}
|
|
QString host;
|
|
QString serviceName{ QHostInfo::localHostName() };
|
|
int port{ JSONAPI_DEFAULT_PORT };
|
|
|
|
// Split hostname and port (or use default port)
|
|
QString givenAddress = argAddress.value(parser);
|
|
if (!NetUtils::resolveHostPort(givenAddress, host, port))
|
|
{
|
|
throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(givenAddress).toStdString());
|
|
}
|
|
|
|
// Search available Hyperion services via mDNS, if default/localhost IP is given
|
|
if (host == "127.0.0.1" || host == "::1")
|
|
{
|
|
#ifndef ENABLE_MDNS
|
|
SSDPDiscover discover;
|
|
host = discover.getFirstService(searchType::STY_FLATBUFSERVER);
|
|
#endif
|
|
QHostAddress address;
|
|
if (!NetUtils::resolveHostToAddress(log, host, address, port))
|
|
{
|
|
throw std::runtime_error(QString("Address could not be resolved for hostname: %2").arg(QSTRING_CSTR(host)).toStdString());
|
|
}
|
|
host = address.toString();
|
|
}
|
|
|
|
Info(log, "Connecting to Hyperion host: %s, port: %u using service: %s", QSTRING_CSTR(host), port, QSTRING_CSTR(serviceName));
|
|
|
|
// create the connection to the hyperion server
|
|
JsonConnection connection(host, parser.isSet(argPrint), port);
|
|
|
|
// authorization token specified. Use it first
|
|
if (parser.isSet(argToken))
|
|
{
|
|
connection.setToken(argToken.value(parser));
|
|
}
|
|
|
|
// If a specific Hyperion instance is given, set it
|
|
if (parser.isSet(argInstance))
|
|
{
|
|
QJsonObject reply = connection.getServerInfo();
|
|
int val = getInstaneIdbyName(reply, argInstance.value(parser));
|
|
connection.setInstance(val);
|
|
}
|
|
|
|
// now execute the given command
|
|
if (parser.isSet(argColor))
|
|
{
|
|
// TODO: make sure setColor accepts a QList<QColor>
|
|
QVector<QColor> _cQV = argColor.getColors(parser).toVector();
|
|
connection.setColor(std::vector<QColor>( _cQV.begin(), _cQV.end() ), argPriority.getInt(parser), argDuration.getInt(parser));
|
|
}
|
|
else if (parser.isSet(argImage))
|
|
{
|
|
QFileInfo imageFile {argImage.getCString(parser)};
|
|
connection.setImage(argImage.getImage(parser), argPriority.getInt(parser), argDuration.getInt(parser), imageFile.fileName());
|
|
}
|
|
#if defined(ENABLE_EFFECTENGINE)
|
|
else if (parser.isSet(argEffect))
|
|
{
|
|
connection.setEffect(argEffect.value(parser), argEffectArgs.value(parser), argPriority.getInt(parser), argDuration.getInt(parser));
|
|
}
|
|
else if (parser.isSet(argCreateEffect))
|
|
{
|
|
connection.createEffect(argCreateEffect.value(parser), argEffectFile.value(parser), argEffectArgs.value(parser));
|
|
}
|
|
else if (parser.isSet(argDeleteEffect))
|
|
{
|
|
connection.deleteEffect(argDeleteEffect.value(parser));
|
|
}
|
|
#endif
|
|
else if (parser.isSet(argServerInfo))
|
|
{
|
|
std::cout << "Server info:\n" << connection.getServerInfoString().toStdString() << std::endl;
|
|
}
|
|
else if (parser.isSet(argSysInfo))
|
|
{
|
|
std::cout << "System info:\n" << connection.getSysInfo().toStdString() << std::endl;
|
|
}
|
|
else if (parser.isSet(argSystemSuspend))
|
|
{
|
|
connection.suspend();
|
|
}
|
|
else if (parser.isSet(argSystemResume))
|
|
{
|
|
connection.resume();
|
|
}
|
|
else if (parser.isSet(argSystemToggleSuspend))
|
|
{
|
|
connection.toggleSuspend();
|
|
}
|
|
else if (parser.isSet(argSystemIdle))
|
|
{
|
|
connection.idle();
|
|
}
|
|
else if (parser.isSet(argSystemToggleIdle))
|
|
{
|
|
connection.toggleIdle();
|
|
}
|
|
else if (parser.isSet(argSystemRestart))
|
|
{
|
|
connection.restart();
|
|
}
|
|
else if (parser.isSet(argClear))
|
|
{
|
|
connection.clear(argPriority.getInt(parser));
|
|
}
|
|
else if (parser.isSet(argClearAll))
|
|
{
|
|
connection.clearAll();
|
|
}
|
|
else if (parser.isSet(argEnableComponent))
|
|
{
|
|
connection.setComponentState(argEnableComponent.value(parser), true);
|
|
}
|
|
else if (parser.isSet(argDisableComponent))
|
|
{
|
|
connection.setComponentState(argDisableComponent.value(parser), false);
|
|
}
|
|
else if (parser.isSet(argOn))
|
|
{
|
|
connection.setComponentState("ALL", true);
|
|
}
|
|
else if (parser.isSet(argOff))
|
|
{
|
|
connection.setComponentState("ALL", false);
|
|
}
|
|
else if (parser.isSet(argSource))
|
|
{
|
|
connection.setSource(argSource.getInt(parser));
|
|
}
|
|
else if (parser.isSet(argSourceAuto))
|
|
{
|
|
connection.setSourceAutoSelect();
|
|
}
|
|
else if (parser.isSet(argConfigGet))
|
|
{
|
|
QString info = connection.getConfig("config");
|
|
std::cout << "Configuration:\n" << info.toStdString() << std::endl;
|
|
}
|
|
else if (parser.isSet(argSchemaGet))
|
|
{
|
|
QString info = connection.getConfig("schema");
|
|
std::cout << "Configuration Schema\n" << info.toStdString() << std::endl;
|
|
}
|
|
else if (parser.isSet(argConfigSet))
|
|
{
|
|
connection.setConfig(argConfigSet.value(parser));
|
|
}
|
|
else if (parser.isSet(argMapping))
|
|
{
|
|
connection.setLedMapping(argMapping.value(parser));
|
|
}
|
|
else if (parser.isSet(argVideoMode))
|
|
{
|
|
connection.setVideoMode(argVideoMode.value(parser));
|
|
}
|
|
else if (colorAdjust)
|
|
{
|
|
connection.setAdjustment(
|
|
argId.value(parser),
|
|
argRAdjust.getColor(parser),
|
|
argGAdjust.getColor(parser),
|
|
argBAdjust.getColor(parser),
|
|
argCAdjust.getColor(parser),
|
|
argMAdjust.getColor(parser),
|
|
argYAdjust.getColor(parser),
|
|
argWAdjust.getColor(parser),
|
|
argbAdjust.getColor(parser),
|
|
argGamma.getDoublePtr(parser),
|
|
argGamma.getDoublePtr(parser),
|
|
argGamma.getDoublePtr(parser),
|
|
argBacklightThreshold.getIntPtr(parser),
|
|
argBacklightColored.getIntPtr(parser),
|
|
argBrightness.getIntPtr(parser),
|
|
argBrightnessC.getIntPtr(parser)
|
|
);
|
|
}
|
|
}
|
|
catch (const std::runtime_error & e)
|
|
{
|
|
// An error occurred. Display error and quit
|
|
Error(log, "%s", e.what());
|
|
return 1;
|
|
}
|
|
|
|
Logger::deleteInstance();
|
|
|
|
return 0;
|
|
}
|