Add Suspend/Resume support (#1535)

* Add Suspend/Resume support

* Support Suspend/Resume/Restart via API, UI and Systray

* Support screen lock/unlock scenario

* Handle idle scenario

* Align with fix for #1368

* Update Windows build

* Refactor SuspendHandler to maintain state

* Do not start BG-Effect, if system goes into suspend mode

* Correct Idle and Resume interaction
This commit is contained in:
LordGrey 2022-12-22 12:40:39 +01:00 committed by GitHub
parent 2217135336
commit 1189f86c1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 994 additions and 67 deletions

View File

@ -151,10 +151,15 @@ jobs:
path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey
key: ${{ runner.os }}-chocolatey key: ${{ runner.os }}-chocolatey
- name: Install Python, OpenSSL, DirectX SDK # - name: Install Python
# shell: powershell
# run: |
# choco install --no-progress python -y
- name: Install OpenSSL, DirectX SDK
shell: powershell shell: powershell
run: | run: |
choco install --no-progress python openssl directx-sdk -y choco install --no-progress openssl directx-sdk -y
- name: Install libjpeg-turbo - name: Install libjpeg-turbo
run: | run: |

View File

@ -116,10 +116,15 @@ jobs:
path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey
key: ${{ runner.os }}-chocolatey key: ${{ runner.os }}-chocolatey
- name: Install Python, OpenSSL, DirectX SDK # - name: Install Python
# shell: powershell
# run: |
# choco install --no-progress python -y
- name: Install OpenSSL, DirectX SDK
shell: powershell shell: powershell
run: | run: |
choco install --no-progress python openssl directx-sdk -y choco install --no-progress openssl directx-sdk -y
- name: Install libjpeg-turbo - name: Install libjpeg-turbo
run: | run: |

View File

@ -10,10 +10,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Allow to Disable / Enable all instances (#970) by
- Suspend/Resume support for Linux and Windows (#1493,#1282, #978).
Suspend/Resume/Restart is supported via API, UI, Systray and hyperion-remote
- Idle scenario via Screen Locking (Linux/Windows), Screensaver invokation (Linux), hyperion-remote or API
In Idle, all instances, components will be disabled besides the output processing (LED-Devices, smoothing).
The current priorities will be cleared and the background effect per instance will be executed, if enabled.
- Commands toogleSuspend and toggleIdle allow to flip between modes, e.g. might be used to trigger modes by a remote
- Add instance# in API response (#1504)
### Changed ### Changed
### Fixed ### Fixed
- Restart correctly, if running as service (#1368)
## Removed ## Removed
## [2.0.14](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.14) - 2022-11 ## [2.0.14](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.14) - 2022-11

View File

@ -13,6 +13,9 @@
"infoDialog_password_current_text": "Current password", "infoDialog_password_current_text": "Current password",
"infoDialog_password_minimum_length": "Passwords must be minimum 8 characters.", "infoDialog_password_minimum_length": "Passwords must be minimum 8 characters.",
"infoDialog_password_new_text": "New password", "infoDialog_password_new_text": "New password",
"InfoDialog_systemSuspend_title": "Suspend",
"InfoDialog_systemResume_title": "Resume",
"InfoDialog_systemRestart_title": "Restart",
"infoDialog_username_text": "Username", "infoDialog_username_text": "Username",
"about_3rd_party_licenses": "3rd party licenses", "about_3rd_party_licenses": "3rd party licenses",
"about_3rd_party_licenses_error": "We had trouble collecting 3rd party licenses information from web. <br />Please follow this link to the GitHub Resource.", "about_3rd_party_licenses_error": "We had trouble collecting 3rd party licenses information from web. <br />Please follow this link to the GitHub Resource.",

View File

@ -213,6 +213,39 @@
</li> </li>
</ul> </ul>
</li> </li>
<!-- /.dropdown -->
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-power-off fa-fw"></i> <i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu dropdown-alerts">
<li id="btn_systemSuspend">
<a>
<div>
<i class="fa fa-stop fa-fw"></i>
<span data-i18n="InfoDialog_systemSuspend_title"></span>
</div>
</a>
</li>
<li id="btn_systemResume">
<a>
<div>
<i class="fa fa-play fa-fw"></i>
<span data-i18n="InfoDialog_systemResume_title"></span>
</div>
</a>
</li>
<li class="divider"></li>
<li id="btn_systemRestart">
<a>
<div>
<i class="fa fa-refresh fa-fw"></i>
<span data-i18n="InfoDialog_systemRestart_title"></span>
</div>
</a>
</li>
</ul>
</li>
<!-- /.lock-ui --> <!-- /.lock-ui -->
<li class="dropdown" id="btn_lock_ui" style="display:none"> <li class="dropdown" id="btn_lock_ui" style="display:none">

View File

@ -318,6 +318,21 @@ function requestSysInfo()
sendToHyperion("sysinfo"); sendToHyperion("sysinfo");
} }
function requestSystemSuspend()
{
sendToHyperion("system","suspend");
}
function requestSystemResume()
{
sendToHyperion("system","resume");
}
function requestSystemRestart()
{
sendToHyperion("system","restart");
}
function requestServerConfigSchema() function requestServerConfigSchema()
{ {
sendToHyperion("config","getschema"); sendToHyperion("config","getschema");

View File

@ -75,6 +75,21 @@ $(document).ready(function () {
changePassword(); changePassword();
}); });
//Suspend Hyperion
$('#btn_systemSuspend').off().on('click', function () {
requestSystemSuspend();
});
//Resume Hyperion
$('#btn_systemResume').off().on('click', function () {
requestSystemResume();
});
//Restart Hyperion
$('#btn_systemRestart').off().on('click', function () {
requestSystemRestart();
});
//Lock Ui //Lock Ui
$('#btn_lock_ui').off().on('click', function () { $('#btn_lock_ui').off().on('click', function () {
removeStorage('loginToken'); removeStorage('loginToken');

View File

@ -99,6 +99,26 @@ signals:
/// ///
void forwardJsonMessage(QJsonObject); void forwardJsonMessage(QJsonObject);
///
/// Signal emits whenever a suspend/resume request for all instances should be forwarded
///
void suspendAll(bool isSuspend);
///
/// Signal emits whenever a toggle suspend/resume request for all instances should be forwarded
///
void toggleSuspendAll();
///
/// Signal emits whenever a idle mode request for all instances should be forwarded
///
void idleAll(bool isIdle);
///
/// Signal emits whenever a toggle idle/working mode request for all instances should be forwarded
///
void toggleIdleAll();
private: private:
// true if further callbacks are forbidden (http) // true if further callbacks are forbidden (http)
bool _noListener; bool _noListener;
@ -298,6 +318,12 @@ private:
/// ///
void handleServiceCommand(const QJsonObject &message, const QString &command, int tan); void handleServiceCommand(const QJsonObject &message, const QString &command, int tan);
/// Handle an incoming JSON message for actions related to the overall Hyperion system
///
/// @param message the incoming message
///
void handleSystemCommand(const QJsonObject &message, const QString &command, int tan);
/// ///
/// Handle an incoming JSON message of unknown type /// Handle an incoming JSON message of unknown type
/// ///

View File

@ -20,6 +20,7 @@ public:
, _hyperion(hyperion) , _hyperion(hyperion)
, _prioMuxer(_hyperion->getMuxerInstance()) , _prioMuxer(_hyperion->getMuxerInstance())
, _isBgEffectEnabled(false) , _isBgEffectEnabled(false)
, _isSuspended(false)
{ {
QString subComponent = parent()->property("instance").toString(); QString subComponent = parent()->property("instance").toString();
_log = Logger::getInstance("HYPERION", subComponent); _log = Logger::getInstance("HYPERION", subComponent);
@ -33,6 +34,11 @@ public:
this->handlePriorityUpdate(); this->handlePriorityUpdate();
}); });
// listen for suspend/resume requests, to not start a background effect when system goes into suspend mode
connect(_hyperion, &Hyperion::suspendRequest, this, [=] (bool isSuspended) {
_isSuspended = isSuspended;
});
// initialization // initialization
handleSettingsUpdate(settings::BGEFFECT, _hyperion->getSetting(settings::BGEFFECT)); handleSettingsUpdate(settings::BGEFFECT, _hyperion->getSetting(settings::BGEFFECT));
} }
@ -109,7 +115,7 @@ private slots:
Debug(_log,"Stop background (color-) effect as it moved out of scope"); Debug(_log,"Stop background (color-) effect as it moved out of scope");
_hyperion->clear(PriorityMuxer::BG_PRIORITY); _hyperion->clear(PriorityMuxer::BG_PRIORITY);
} }
else if (_prioMuxer->getCurrentPriority() == PriorityMuxer::LOWEST_PRIORITY && _isBgEffectEnabled) else if (!_isSuspended && _prioMuxer->getCurrentPriority() == PriorityMuxer::LOWEST_PRIORITY && _isBgEffectEnabled)
{ {
Debug(_log,"Start background (color-) effect as it moved in scope"); Debug(_log,"Start background (color-) effect as it moved in scope");
emit handleSettingsUpdate (settings::BGEFFECT, _bgEffectConfig); emit handleSettingsUpdate (settings::BGEFFECT, _bgEffectConfig);
@ -126,6 +132,8 @@ private:
QJsonDocument _bgEffectConfig; QJsonDocument _bgEffectConfig;
bool _isBgEffectEnabled; bool _isBgEffectEnabled;
bool _isSuspended;
}; };
#endif // BGEFFECTHANDLER_H #endif // BGEFFECTHANDLER_H

View File

@ -7,12 +7,15 @@
#include <map> #include <map>
#include <QObject> #include <QObject>
#include <QVector>
class Hyperion; class Hyperion;
typedef QVector<hyperion::Components> ComponentList;
/// ///
/// @brief The component register reflects and manages the current state of all components and Hyperion as a whole /// @brief The component register reflects and manages the current state of all components and Hyperion as a whole
/// It emits also real component state changes (triggert from the specific component), which can be used for listening APIs (Network Clients/Plugins) /// It emits also real component state changes (triggered from the specific component), which can be used for listening APIs (Network Clients/Plugins)
/// ///
class ComponentRegister : public QObject class ComponentRegister : public QObject
{ {
@ -36,23 +39,32 @@ signals:
/// ///
/// @brief Emits whenever a component changed (really) the state /// @brief Emits whenever a component changed (really) the state
/// @param comp The component /// @param comp The component
/// @param state The new state of the component /// @param isActive The new state of the component
/// ///
void updatedComponentState(hyperion::Components comp, bool state); void updatedComponentState(hyperion::Components comp, bool isActive);
public slots: public slots:
/// ///
/// @brief is called whenever a component change a state, DO NOT CALL FROM API, use signal hyperion->compStateChangeRequest /// @brief is called whenever a component change a state, DO NOT CALL FROM API, use signal hyperion->compStateChangeRequest
/// @param comp The component /// @param comp The component
/// @param state The new state of the component /// @param isActive The new state of the component
/// ///
void setNewComponentState(hyperion::Components comp, bool activated); void setNewComponentState(hyperion::Components comp, bool isActive);
private slots: private slots:
/// ///
/// @brief Handle COMP_ALL changes from Hyperion->compStateChangeRequest /// @brief Handle COMP_ALL changes from Hyperion->compStateChangeRequest
/// @param comp COMP_ALL
/// @param isActive The new state for all components
/// ///
void handleCompStateChangeRequest(hyperion::Components comps, bool activated); void handleCompStateChangeRequest(hyperion::Components comps, bool isActive);
///
/// @brief Activate/Deactivate all components, except those provided by the list of excluded components
/// @param isActive The new state for all components
/// @param execludeList of excluded components
///
void handleCompStateChangeRequestAll(bool isActive, const ComponentList& excludeList = ComponentList{});
private: private:
/// Hyperion instance /// Hyperion instance

View File

@ -389,6 +389,20 @@ public slots:
int getLatchTime() const; int getLatchTime() const;
///
/// @brief Set hyperion in suspend mode or resume from suspend/idle.
/// All instances and components will be disabled/enabled.
/// @param isSupend True, components will be deactivated, else put into their previous state before suspend
///
void setSuspend(bool isSupend);
///
/// @brief Set hyperion in idle /working mode.
/// In idle, all instances and components will be disabled besides the output processing (LED-Devices, smoothing).
/// @param isIdle True, selected components will be deactivated, else put into their previous state before idle
///
void setIdle(bool isIdle);
signals: signals:
/// Signal which is emitted when a priority channel is actively cleared /// Signal which is emitted when a priority channel is actively cleared
/// This signal will not be emitted when a priority channel time out /// This signal will not be emitted when a priority channel time out
@ -406,6 +420,18 @@ signals:
/// ///
void compStateChangeRequest(hyperion::Components component, bool enabled); void compStateChangeRequest(hyperion::Components component, bool enabled);
///
/// @brief Emits when all (besides excluded) components are subject to state changes
/// @param isActive The new state for all components
/// @param execlude List of excluded components
void compStateChangeRequestAll(bool isActive, const ComponentList& excludeList = {});
/// Signal which is emitted, when system is to be suspended/resumed
void suspendRequest(bool isSuspend);
/// Signal which is emitted, when system should go into idle/working mode
void idleRequest(bool isIdle);
/// ///
/// @brief Emits whenever the imageToLedsMapping has changed /// @brief Emits whenever the imageToLedsMapping has changed
/// @param mappingType The new mapping type /// @param mappingType The new mapping type

View File

@ -74,10 +74,26 @@ public slots:
bool stopInstance(quint8 inst); bool stopInstance(quint8 inst);
/// ///
/// @brief Toggle the state of all Hyperion instances /// @brief Suspend (disable) all Hyperion instances
/// @param pause If true all instances toggle to pause, else to resume
/// ///
void toggleStateAllInstances(bool pause = false); void suspend();
///
/// @brief Resume (resume) all Hyperion instances
///
void resume();
///
/// @brief Toggle the state of all Hyperion instances for an idle sceanrio (user is not interacting with the system
/// @param isIdle, If true all instances toggle to idle, else to resume
///
void toggleIdle(bool isIdle);
///
/// @brief Toggle the state of all Hyperion instances
/// @param enable, If false all instances toggle to pause, else to resume
///
void toggleStateAllInstances(bool enable = false);
/// ///
/// @brief Create a new Hyperion instance entry in db /// @brief Create a new Hyperion instance entry in db
@ -125,6 +141,11 @@ signals:
/// ///
void startInstanceResponse(QObject *caller, const int &tan); void startInstanceResponse(QObject *caller, const int &tan);
void triggerSuspend(bool isSuspend);
void triggerToggleSuspend();
void triggerIdle(bool isIdle);
void triggerToggleIdle();
signals: signals:
/////////////////////////////////////// ///////////////////////////////////////
/// FROM HYPERIONDAEMON TO HYPERION /// /// FROM HYPERIONDAEMON TO HYPERION ///

View File

@ -0,0 +1,20 @@
{
"type":"object",
"required":true,
"properties":{
"command": {
"type" : "string",
"required" : true,
"enum" : ["system"]
},
"tan" : {
"type" : "integer"
},
"subcommand": {
"type" : "string",
"required" : true,
"enum": [ "restart", "resume", "suspend", "toggleSuspend", "idle", "toggleIdle" ]
}
},
"additionalProperties": false
}

View File

@ -5,7 +5,7 @@
"command": { "command": {
"type" : "string", "type" : "string",
"required" : true, "required" : true,
"enum": [ "color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "leddevice", "inputsource", "service", "transform", "correction", "temperature" ] "enum": [ "color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "leddevice", "inputsource", "service", "system", "transform", "correction", "temperature" ]
} }
} }
} }

View File

@ -23,6 +23,7 @@
<file alias="schema-leddevice">JSONRPC_schema/schema-leddevice.json</file> <file alias="schema-leddevice">JSONRPC_schema/schema-leddevice.json</file>
<file alias="schema-inputsource">JSONRPC_schema/schema-inputsource.json</file> <file alias="schema-inputsource">JSONRPC_schema/schema-inputsource.json</file>
<file alias="schema-service">JSONRPC_schema/schema-service.json</file> <file alias="schema-service">JSONRPC_schema/schema-service.json</file>
<file alias="schema-system">JSONRPC_schema/schema-system.json</file>
<!-- The following schemas are derecated but used to ensure backward compatibility with hyperion Classic remote control--> <!-- The following schemas are derecated but used to ensure backward compatibility with hyperion Classic remote control-->
<file alias="schema-transform">JSONRPC_schema/schema-hyperion-classic.json</file> <file alias="schema-transform">JSONRPC_schema/schema-hyperion-classic.json</file>
<file alias="schema-correction">JSONRPC_schema/schema-hyperion-classic.json</file> <file alias="schema-correction">JSONRPC_schema/schema-hyperion-classic.json</file>

View File

@ -120,6 +120,12 @@ void JsonAPI::initialize()
_jsonCB->setSubscriptionsTo(_hyperion); _jsonCB->setSubscriptionsTo(_hyperion);
connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage); connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage);
} }
//notify instance manager on suspend/resume/idle requests
connect(this, &JsonAPI::suspendAll, _instanceManager, &HyperionIManager::triggerSuspend);
connect(this, &JsonAPI::toggleSuspendAll, _instanceManager, &HyperionIManager::triggerToggleSuspend);
connect(this, &JsonAPI::idleAll, _instanceManager, &HyperionIManager::triggerIdle);
connect(this, &JsonAPI::toggleIdleAll, _instanceManager, &HyperionIManager::triggerToggleIdle);
} }
bool JsonAPI::handleInstanceSwitch(quint8 inst, bool forced) bool JsonAPI::handleInstanceSwitch(quint8 inst, bool forced)
@ -236,6 +242,8 @@ proceed:
handleInputSourceCommand(message, command, tan); handleInputSourceCommand(message, command, tan);
else if (command == "service") else if (command == "service")
handleServiceCommand(message, command, tan); handleServiceCommand(message, command, tan);
else if (command == "system")
handleSystemCommand(message, command, tan);
// BEGIN | The following commands are deprecated but used to ensure backward compatibility with hyperion Classic remote control // BEGIN | The following commands are deprecated but used to ensure backward compatibility with hyperion Classic remote control
else if (command == "clearall") else if (command == "clearall")
@ -1799,6 +1807,49 @@ void JsonAPI::handleServiceCommand(const QJsonObject &message, const QString &co
} }
} }
void JsonAPI::handleSystemCommand(const QJsonObject &message, const QString &command, int tan)
{
DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData());
const QString &subc = message["subcommand"].toString().trimmed();
if (subc == "suspend")
{
emit suspendAll(true);
sendSuccessReply(command + "-" + subc, tan);
}
else if (subc == "resume")
{
emit suspendAll(false);
sendSuccessReply(command + "-" + subc, tan);
}
else if (subc == "restart")
{
Process::restartHyperion(11);
sendSuccessReply(command + "-" + subc, tan);
}
else if (subc == "toggleSuspend")
{
emit toggleSuspendAll();
sendSuccessReply(command + "-" + subc, tan);
}
else if (subc == "idle")
{
emit idleAll(true);
sendSuccessReply(command + "-" + subc, tan);
}
else if (subc == "toggleIdle")
{
emit toggleIdleAll();
sendSuccessReply(command + "-" + subc, tan);
}
else
{
QString full_command = command + "-" + subc;
sendErrorReply("Unknown or missing subcommand", full_command, tan);
}
}
void JsonAPI::handleNotImplemented(const QString &command, int tan) void JsonAPI::handleNotImplemented(const QString &command, int tan)
{ {
sendErrorReply("Command not implemented", command, tan); sendErrorReply("Command not implemented", command, tan);

View File

@ -55,12 +55,13 @@ ComponentRegister::ComponentRegister(Hyperion* hyperion)
vect << COMP_FORWARDER; vect << COMP_FORWARDER;
#endif #endif
for(auto e : vect) for(auto e : qAsConst(vect))
{ {
_componentStates.emplace(e, (e == COMP_ALL)); _componentStates.emplace(e, (e == COMP_ALL));
} }
connect(_hyperion, &Hyperion::compStateChangeRequest, this, &ComponentRegister::handleCompStateChangeRequest); connect(_hyperion, &Hyperion::compStateChangeRequest, this, &ComponentRegister::handleCompStateChangeRequest);
connect(_hyperion, &Hyperion::compStateChangeRequestAll, this, &ComponentRegister::handleCompStateChangeRequestAll);
} }
ComponentRegister::~ComponentRegister() ComponentRegister::~ComponentRegister()
@ -72,30 +73,48 @@ int ComponentRegister::isComponentEnabled(hyperion::Components comp) const
return (_componentStates.count(comp)) ? _componentStates.at(comp) : -1; return (_componentStates.count(comp)) ? _componentStates.at(comp) : -1;
} }
void ComponentRegister::setNewComponentState(hyperion::Components comp, bool activated) void ComponentRegister::setNewComponentState(hyperion::Components comp, bool isActive)
{ {
if (_componentStates.count(comp) > 0) if (_componentStates.count(comp) > 0)
{ {
if (_componentStates[comp] != activated) if (_componentStates[comp] != isActive)
{ {
Debug(_log, "%s: %s", componentToString(comp), (activated ? "enabled" : "disabled")); Debug(_log, "%s: %s", componentToString(comp), (isActive ? "enabled" : "disabled"));
_componentStates[comp] = activated; _componentStates[comp] = isActive;
// emit component has changed state // emit component has changed state
emit updatedComponentState(comp, activated); emit updatedComponentState(comp, isActive);
} }
} }
} }
void ComponentRegister::handleCompStateChangeRequest(hyperion::Components comps, bool activated) void ComponentRegister::handleCompStateChangeRequest(hyperion::Components comps, bool isActive)
{ {
if(comps == COMP_ALL && !_inProgress) if(comps == COMP_ALL )
{
handleCompStateChangeRequestAll(isActive,{});
}
}
void ComponentRegister::handleCompStateChangeRequestAll(bool isActive, const ComponentList& excludeList)
{
if (!_inProgress)
{ {
_inProgress = true; _inProgress = true;
if(!activated && _prevComponentStates.empty()) if(!isActive)
{ {
Debug(_log,"Disable Hyperion, store current component states"); if (excludeList.isEmpty())
{
Debug(_log,"Disable Hyperion instance, store current components' state");
}
else
{
Debug(_log,"Disable selected Hyperion components, store their current state");
}
for(const auto &comp : _componentStates) for(const auto &comp : _componentStates)
{
if (!excludeList.contains(comp.first) && comp.first != COMP_ALL)
{ {
// save state // save state
_prevComponentStates.emplace(comp.first, comp.second); _prevComponentStates.emplace(comp.first, comp.second);
@ -105,14 +124,29 @@ void ComponentRegister::handleCompStateChangeRequest(hyperion::Components comps,
emit _hyperion->compStateChangeRequest(comp.first, false); emit _hyperion->compStateChangeRequest(comp.first, false);
} }
} }
}
if (excludeList.isEmpty())
{
setNewComponentState(COMP_ALL, false); setNewComponentState(COMP_ALL, false);
} }
}
else else
{ {
if(activated && !_prevComponentStates.empty()) if(isActive && !_prevComponentStates.empty())
{ {
Debug(_log,"Enable Hyperion, recover previous component states"); if (excludeList.isEmpty())
{
Debug(_log,"Enable Hyperion instance, restore components' previous state");
}
else
{
Debug(_log,"Enable selected Hyperion components, restore their previous state");
}
for(const auto &comp : _prevComponentStates) for(const auto &comp : _prevComponentStates)
{
if (!excludeList.contains(comp.first) && comp.first != COMP_ALL)
{ {
// if comp was enabled, enable again // if comp was enabled, enable again
if(comp.second) if(comp.second)
@ -120,10 +154,14 @@ void ComponentRegister::handleCompStateChangeRequest(hyperion::Components comps,
emit _hyperion->compStateChangeRequest(comp.first, true); emit _hyperion->compStateChangeRequest(comp.first, true);
} }
} }
}
_prevComponentStates.clear(); _prevComponentStates.clear();
if (excludeList.isEmpty())
{
setNewComponentState(COMP_ALL, true); setNewComponentState(COMP_ALL, true);
} }
} }
}
_inProgress = false; _inProgress = false;
} }
} }

View File

@ -75,6 +75,8 @@ Hyperion::Hyperion(quint8 instance, bool readonlyMode)
#endif #endif
, _readOnlyMode(readonlyMode) , _readOnlyMode(readonlyMode)
{ {
qRegisterMetaType<ComponentList>("ComponentList");
QString subComponent = "I"+QString::number(instance); QString subComponent = "I"+QString::number(instance);
this->setProperty("instance", (QString) subComponent); this->setProperty("instance", (QString) subComponent);
@ -117,8 +119,9 @@ void Hyperion::start()
connect(_muxer, &PriorityMuxer::visiblePriorityChanged, this, &Hyperion::handleSourceAvailability); connect(_muxer, &PriorityMuxer::visiblePriorityChanged, this, &Hyperion::handleSourceAvailability);
connect(_muxer, &PriorityMuxer::visibleComponentChanged, this, &Hyperion::handleVisibleComponentChanged); connect(_muxer, &PriorityMuxer::visibleComponentChanged, this, &Hyperion::handleVisibleComponentChanged);
// listens for ComponentRegister changes of COMP_ALL to perform core enable/disable actions // listen for suspend/resume, idle requests to perform core activation/deactivation actions
// connect(&_componentRegister, &ComponentRegister::updatedComponentState, this, &Hyperion::updatedComponentState); connect(this, &Hyperion::suspendRequest, this, &Hyperion::setSuspend);
connect(this, &Hyperion::idleRequest, this, &Hyperion::setIdle);
// listen for settings updates of this instance (LEDS & COLOR) // listen for settings updates of this instance (LEDS & COLOR)
connect(_settingsManager, &SettingsManager::settingsChanged, this, &Hyperion::handleSettingsUpdate); connect(_settingsManager, &SettingsManager::settingsChanged, this, &Hyperion::handleSettingsUpdate);
@ -377,6 +380,20 @@ int Hyperion::isComponentEnabled(hyperion::Components comp) const
return _componentRegister->isComponentEnabled(comp); return _componentRegister->isComponentEnabled(comp);
} }
void Hyperion::setSuspend(bool isSuspend)
{
bool enable = !isSuspend;
emit compStateChangeRequestAll(enable);
}
void Hyperion::setIdle(bool isIdle)
{
clear(-1);
bool enable = !isIdle;
emit compStateChangeRequestAll(enable, {hyperion::COMP_LEDDEVICE, hyperion::COMP_SMOOTHING} );
}
void Hyperion::registerInput(int priority, hyperion::Components component, const QString& origin, const QString& owner, unsigned smooth_cfg) void Hyperion::registerInput(int priority, hyperion::Components component, const QString& origin, const QString& owner, unsigned smooth_cfg)
{ {
_muxer->registerInput(priority, component, origin, owner, smooth_cfg); _muxer->registerInput(priority, component, origin, owner, smooth_cfg);

View File

@ -63,13 +63,43 @@ void HyperionIManager::stopAll()
} }
} }
void HyperionIManager::toggleStateAllInstances(bool pause) void HyperionIManager::suspend()
{
Info(_log,"Suspend all instances and enabled components");
QMap<quint8, Hyperion*> instCopy = _runningInstances;
for(const auto instance : instCopy)
{
emit instance->suspendRequest(true);
}
}
void HyperionIManager::resume()
{
Info(_log,"Resume all instances and enabled components");
QMap<quint8, Hyperion*> instCopy = _runningInstances;
for(const auto instance : instCopy)
{
emit instance->suspendRequest(false);
}
}
void HyperionIManager::toggleIdle(bool isIdle)
{
Info(_log,"Put all instances in %s state", isIdle ? "idle" : "working");
QMap<quint8, Hyperion*> instCopy = _runningInstances;
for(const auto instance : instCopy)
{
emit instance->idleRequest(isIdle);
}
}
void HyperionIManager::toggleStateAllInstances(bool enable)
{ {
// copy the instances due to loop corruption, even with .erase() return next iter // copy the instances due to loop corruption, even with .erase() return next iter
QMap<quint8, Hyperion*> instCopy = _runningInstances; QMap<quint8, Hyperion*> instCopy = _runningInstances;
for(const auto instance : instCopy) for(const auto instance : instCopy)
{ {
emit instance->compStateChangeRequest(hyperion::COMP_ALL, pause); emit instance->compStateChangeRequest(hyperion::COMP_ALL, enable);
} }
} }

View File

@ -0,0 +1 @@
<svg width="512px" height="512px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><title>ionicons-v5-c</title><polygon points="96 448 416 256 96 64 96 448"/></svg>

After

Width:  |  Height:  |  Size: 172 B

View File

@ -0,0 +1,3 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><title>ionicons-v5-c</title><rect x="80" y="80" width="352" height="352"/></svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@ -251,6 +251,78 @@ QString JsonConnection::getSysInfo()
return QString(); return QString();
} }
void JsonConnection::suspend()
{
Info(_log, "Suspend Hyperion. Stop all instances and components");
QJsonObject command;
command["command"] = QString("system");
command["subcommand"] = QString("suspend");
QJsonObject reply = sendMessage(command);
parseReply(reply);
}
void JsonConnection::resume()
{
Info(_log, "Resume Hyperion. Start all instances and components");
QJsonObject command;
command["command"] = QString("system");
command["subcommand"] = QString("resume");
QJsonObject reply = sendMessage(command);
parseReply(reply);
}
void JsonConnection::toggleSuspend()
{
Info(_log, "Toggle between Suspend and Resume");
QJsonObject command;
command["command"] = QString("system");
command["subcommand"] = QString("toggleSuspend");
QJsonObject reply = sendMessage(command);
parseReply(reply);
}
void JsonConnection::idle()
{
Info(_log, "Put Hyperion in Idle mode.");
QJsonObject command;
command["command"] = QString("system");
command["subcommand"] = QString("idle");
QJsonObject reply = sendMessage(command);
parseReply(reply);
}
void JsonConnection::toggleIdle()
{
Info(_log, "Toggle between Idle and Working mode");
QJsonObject command;
command["command"] = QString("system");
command["subcommand"] = QString("toggleIdle");
QJsonObject reply = sendMessage(command);
parseReply(reply);
}
void JsonConnection::restart()
{
Info(_log, "Restart Hyperion...");
QJsonObject command;
command["command"] = QString("system");
command["subcommand"] = QString("restart");
QJsonObject reply = sendMessage(command);
parseReply(reply);
}
void JsonConnection::clear(int priority) void JsonConnection::clear(int priority)
{ {
Debug(_log, "Clear priority channel [%d]", priority); Debug(_log, "Clear priority channel [%d]", priority);

View File

@ -99,6 +99,36 @@ public:
/// ///
QString getSysInfo(); QString getSysInfo();
///
/// Suspend Hyperion. Stop all instances and components
///
void suspend();
///
/// Resume Hyperion. Start all instances and components
///
void resume();
///
/// Toggle between Suspend and Resume
///
void toggleSuspend();
///
/// Put Hyperion in Idle mode, i.e. all instances, components will be disabled besides the output processing (LED-devices, smoothing).
///
void idle();
///
/// Toggle between Idle and Working mode
///
void toggleIdle();
///
/// Restart Hyperion
///
void restart();
/// ///
/// Clear the given priority channel /// Clear the given priority channel
/// ///

View File

@ -119,8 +119,6 @@ int main(int argc, char * argv[])
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 & 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."); Option & argDeleteEffect = parser.add<Option> (0x0, "deleteEffect" , "Delete a custom created JSON Effect configuration file.");
#endif #endif
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 & argClear = parser.add<BooleanOption>('x', "clear" , "Clear data for the priority channel provided by the -p option"); 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"); 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, LEDDEVICE]"); Option & argEnableComponent = parser.add<Option> ('E', "enable" , "Enable the Component with the given name. Available Components are [SMOOTHING, BLACKBORDER, FORWARDER, BOBLIGHTSERVER, GRABBER, V4L, LEDDEVICE]");
@ -148,6 +146,14 @@ int main(int argc, char * argv[])
BooleanOption & argConfigGet = parser.add<BooleanOption>(0x0, "configGet" , "Print the current loaded Hyperion configuration file"); 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"); 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."); 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 & 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 & argDebug = parser.add<BooleanOption>(0x0, "debug", "Enable debug logging");
@ -178,7 +184,11 @@ int main(int argc, char * argv[])
#if defined(ENABLE_EFFECTENGINE) #if defined(ENABLE_EFFECTENGINE)
parser.isSet(argEffect), parser.isSet(argCreateEffect), parser.isSet(argDeleteEffect), parser.isSet(argEffect), parser.isSet(argCreateEffect), parser.isSet(argDeleteEffect),
#endif #endif
parser.isSet(argServerInfo), parser.isSet(argSysInfo),parser.isSet(argClear), parser.isSet(argClearAll), parser.isSet(argEnableComponent), parser.isSet(argDisableComponent), colorAdjust, 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(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) }); parser.isSet(argMapping),parser.isSet(argVideoMode) });
if (commandCount != 1) if (commandCount != 1)
@ -193,6 +203,12 @@ int main(int argc, char * argv[])
#endif #endif
showHelp(argServerInfo); showHelp(argServerInfo);
showHelp(argSysInfo); showHelp(argSysInfo);
showHelp(argSystemSuspend);
showHelp(argSystemResume);
showHelp(argSystemToggleSuspend);
showHelp(argSystemIdle);
showHelp(argSystemToggleIdle);
showHelp(argSystemRestart);
showHelp(argClear); showHelp(argClear);
showHelp(argClearAll); showHelp(argClearAll);
showHelp(argEnableComponent); showHelp(argEnableComponent);
@ -295,6 +311,30 @@ int main(int argc, char * argv[])
{ {
std::cout << "System info:\n" << connection.getSysInfo().toStdString() << std::endl; 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)) else if (parser.isSet(argClear))
{ {
connection.clear(argPriority.getInt(parser)); connection.clear(argPriority.getInt(parser));

View File

@ -30,17 +30,29 @@ if (APPLE)
set_source_files_properties(${BUNDLE_RESOURCE_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) set_source_files_properties(${BUNDLE_RESOURCE_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
endif(APPLE) endif(APPLE)
if (UNIX)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS DBus REQUIRED )
if (Qt${QT_VERSION_MAJOR}DBus_FOUND)
set(hyperiond_POWER_MNG_DBUS "Qt${QT_VERSION_MAJOR}::DBus")
endif()
endif(UNIX)
add_executable(${PROJECT_NAME} add_executable(${PROJECT_NAME}
console.h console.h
hyperiond.h hyperiond.h
systray.h systray.h
hyperiond.cpp hyperiond.cpp
systray.cpp systray.cpp
SuspendHandler.cpp
main.cpp main.cpp
${hyperiond_WIN_RC_PATH} ${hyperiond_WIN_RC_PATH}
${BUNDLE_RESOURCE_FILES} ${BUNDLE_RESOURCE_FILES}
) )
if (UNIX AND NOT APPLE AND Qt${QT_VERSION_MAJOR}DBus_FOUND)
target_compile_definitions(${PROJECT_NAME} PUBLIC HYPERION_HAS_DBUS)
endif()
# promote hyperiond as GUI app # promote hyperiond as GUI app
if (WIN32) if (WIN32)
target_link_options(${PROJECT_NAME} PUBLIC /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup) target_link_options(${PROJECT_NAME} PUBLIC /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup)
@ -58,6 +70,7 @@ target_link_libraries(${PROJECT_NAME}
Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Widgets
${hyperiond_POWER_MNG_DBUS}
) )
if(ENABLE_EFFECTENGINE) if(ENABLE_EFFECTENGINE)

View File

@ -0,0 +1,312 @@
#include "SuspendHandler.h"
#include <QtGlobal>
#include <hyperion/HyperionIManager.h>
#include <iostream>
SuspendHandlerBase::SuspendHandlerBase()
: _isSuspended(false)
, _isIdle(false)
, _isLocked (false)
{
// Trigger suspend/resume/idle scenarios to be executed by Instance mMnager
connect(this, &SuspendHandlerBase::suspendEvent, HyperionIManager::getInstance(), &HyperionIManager::suspend);
connect(this, &SuspendHandlerBase::resumeEvent, HyperionIManager::getInstance(), &HyperionIManager::resume);
connect(this, &SuspendHandlerBase::lockedEvent, HyperionIManager::getInstance(), &HyperionIManager::toggleIdle);
connect(this, &SuspendHandlerBase::idleEvent, HyperionIManager::getInstance(), &HyperionIManager::toggleIdle);
// Listen to suspend/resume/idle events received by the Instance Manager via API
connect(HyperionIManager::getInstance(), &HyperionIManager::triggerSuspend, this, QOverload<bool>::of(&SuspendHandler::suspend));
connect(HyperionIManager::getInstance(), &HyperionIManager::triggerToggleSuspend, this, &SuspendHandler::toggleSuspend);
connect(HyperionIManager::getInstance(), &HyperionIManager::triggerIdle, this, &SuspendHandler::idle);
connect(HyperionIManager::getInstance(), &HyperionIManager::triggerToggleIdle, this, &SuspendHandler::toggleIdle);
}
SuspendHandlerBase::~SuspendHandlerBase()
{
}
void SuspendHandlerBase::suspend()
{
Debug(Logger::getInstance("DAEMON"), "");
suspend(true);
}
void SuspendHandlerBase::suspend(bool sleep)
{
if (sleep)
{
if (!_isSuspended)
{
_isSuspended = true;
Info(Logger::getInstance("DAEMON"), "Suspend event received - Hyperion is going to sleep");
emit suspendEvent();
}
else
{
Debug(Logger::getInstance("DAEMON"), "Suspend event ignored - already suspended");
}
}
else
{
if (_isSuspended || _isIdle)
{
Info(Logger::getInstance("DAEMON"), "Resume event received - Hyperion is going into working mode");
emit resumeEvent();
_isSuspended = false;
_isIdle = false;
}
else
{
Debug(Logger::getInstance("DAEMON"), "Resume event ignored - not in suspend nor idle mode");
}
}
}
void SuspendHandlerBase::resume()
{
Debug(Logger::getInstance("DAEMON"), "");
suspend(false);
}
void SuspendHandlerBase::toggleSuspend()
{
Debug(Logger::getInstance("DAEMON"), "Toggle suspend event received");
if (!_isSuspended)
{
suspend(true);
}
else
{
suspend(false);
}
}
void SuspendHandlerBase::idle(bool isIdle)
{
if (!_isSuspended)
{
if (isIdle)
{
if (!_isIdle)
{
_isIdle = true;
Info(Logger::getInstance("DAEMON"), "Idle event received");
emit idleEvent(isIdle);
}
}
else
{
if (_isIdle)
{
Info(Logger::getInstance("DAEMON"), "Resume from idle event recevied");
emit idleEvent(isIdle);
_isIdle = false;
}
}
}
else
{
Debug(Logger::getInstance("DAEMON"), "Idle event ignored - Hyperion is suspended");
}
}
void SuspendHandlerBase::toggleIdle()
{
Debug(Logger::getInstance("DAEMON"), "Toggle idle event received");
if (!_isIdle)
{
idle(true);
}
else
{
idle(false);
}
}
void SuspendHandlerBase::lock(bool isLocked)
{
if (!_isSuspended)
{
if (isLocked)
{
if (!_isLocked)
{
_isLocked = true;
Info(Logger::getInstance("DAEMON"), "Screen lock event received");
emit lockedEvent(isLocked);
}
}
else
{
if (_isLocked)
{
Info(Logger::getInstance("DAEMON"), "Screen unlock event received");
emit lockedEvent(isLocked);
_isLocked = false;
}
}
}
else
{
Debug(Logger::getInstance("DAEMON"), "Screen lock event ignored - Hyperion is suspended");
}
}
#if defined(_WIN32)
#include <QCoreApplication>
#include <QWidget>
#include <windows.h>
#include <wtsapi32.h>
#pragma comment( lib, "wtsapi32.lib" )
SuspendHandlerWindows::SuspendHandlerWindows()
{
auto handle = reinterpret_cast<HWND> (_widget.winId());
_notifyHandle = RegisterSuspendResumeNotification(handle, DEVICE_NOTIFY_WINDOW_HANDLE);
if (_notifyHandle != NULL)
{
QCoreApplication::instance()->installNativeEventFilter(this);
}
else
{
Error(Logger::getInstance("DAEMON"), "Could not register for suspend/resume events!");
}
if (!WTSRegisterSessionNotification(handle, NOTIFY_FOR_THIS_SESSION))
{
Error(Logger::getInstance("DAEMON"), "Could not register for lock/unlock events!");
}
}
SuspendHandlerWindows::~SuspendHandlerWindows()
{
if (_notifyHandle != NULL)
{
QCoreApplication::instance()->removeNativeEventFilter(this);
UnregisterSuspendResumeNotification(_notifyHandle);
auto handle = reinterpret_cast<HWND> (_widget.winId());
WTSUnRegisterSessionNotification(handle);
}
_notifyHandle = NULL;
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
bool SuspendHandlerWindows::nativeEventFilter(const QByteArray& eventType, void* message, qintptr* /*result*/)
#else
bool SuspendHandlerWindows::nativeEventFilter(const QByteArray& eventType, void* message, long int* /*result*/)
#endif
{
MSG* msg = static_cast<MSG*>(message);
switch (msg->message)
{
case WM_WTSSESSION_CHANGE:
switch (msg->wParam)
{
case WTS_SESSION_LOCK:
emit lock(true);
return true;
break;
case WTS_SESSION_UNLOCK:
emit lock(false);
return true;
break;
}
break;
case WM_POWERBROADCAST:
switch (msg->wParam)
{
case PBT_APMRESUMESUSPEND:
emit suspend(false);
return true;
break;
case PBT_APMSUSPEND:
emit suspend(true);
return true;
break;
}
break;
}
return false;
}
#elif defined(__linux__) && defined(HYPERION_HAS_DBUS)
#include <QDBusConnection>
struct dBusSignals
{
QString service;
QString path;
QString interface;
QString name;
};
typedef QMultiMap<QString, dBusSignals> DbusSignalsMap;
// Constants
namespace {
const DbusSignalsMap dbusSignals = {
//system signals
{"Suspend", {"org.freedesktop.login1","/org/freedesktop/login1","org.freedesktop.login1.Manager","PrepareForSleep"}},
//Session signals
{"ScreenSaver", {"org.freedesktop.ScreenSaver","/org/freedesktop/ScreenSaver","org.freedesktop.ScreenSaver","ActiveChanged"}},
{"ScreenSaver", {"org.gnome.ScreenSaver","/org/gnome/ScreenSaver","org.gnome.ScreenSaver","ActiveChanged"}},
};
} //End of constants
SuspendHandlerLinux::SuspendHandlerLinux()
{
QDBusConnection systemBus = QDBusConnection::systemBus();
if (!systemBus.isConnected())
{
Error(Logger::getInstance("DAEMON"), "Suspend/resume handler - System bus is not connected");
}
else
{
QString service = dbusSignals.find("Suspend").value().service;
if (!systemBus.connect(service,
dbusSignals.find("Suspend").value().path,
dbusSignals.find("Suspend").value().interface,
dbusSignals.find("Suspend").value().name,
this, SLOT(suspend(bool))))
Error(Logger::getInstance("DAEMON"), "Could not register for suspend/resume events [%s]!", QSTRING_CSTR(service));
else
{
Debug(Logger::getInstance("DAEMON"), "Registered for suspend/resume events [%s].", QSTRING_CSTR(service));
}
}
QDBusConnection sessionBus = QDBusConnection::sessionBus();
if (!sessionBus.isConnected())
{
Error(Logger::getInstance("DAEMON"), "Lock/unlock handler- Session bus is not connected");
}
else
{
DbusSignalsMap::const_iterator iter = dbusSignals.find("ScreenSaver");
while (iter != dbusSignals.end() && iter.key() == "ScreenSaver") {
QString service = iter.value().service;
if (!sessionBus.connect(service,
iter.value().path,
iter.value().interface,
iter.value().name,
this, SLOT(lock(bool))))
Error(Logger::getInstance("DAEMON"), "Could not register for lock/unlock events [%s]!", QSTRING_CSTR(service));
else
{
Debug(Logger::getInstance("DAEMON"), "Registered for lock/unlock events [%s].", QSTRING_CSTR(service));
}
++iter;
}
}
}
#endif

View File

@ -0,0 +1,79 @@
#ifndef SUSPENDHANDLER_H
#define SUSPENDHANDLER_H
#include <QObject>
class SuspendHandlerBase : public QObject {
Q_OBJECT
public:
SuspendHandlerBase();
virtual ~SuspendHandlerBase() override;
public slots:
void suspend(bool sleep);
void suspend();
void resume();
void toggleSuspend();
void idle(bool isIdle);
void toggleIdle();
void lock(bool isLocked);
signals:
void suspendEvent();
void resumeEvent();
void lockedEvent(bool);
void idleEvent(bool);
private:
bool _isSuspended;
bool _isIdle;
bool _isLocked;
};
#if defined(_WIN32)
#include <QAbstractNativeEventFilter>
#include <QAbstractEventDispatcher>
#include <QWidget>
#include <windows.h>
class SuspendHandlerWindows : public SuspendHandlerBase, public QAbstractNativeEventFilter {
public:
SuspendHandlerWindows();
~SuspendHandlerWindows() override;
protected:
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
bool nativeEventFilter(const QByteArray& eventType, void* message, qintptr* result) override;
#else
bool nativeEventFilter(const QByteArray& eventType, void* message, long int* result) override;
#endif
private:
QWidget _widget;
HPOWERNOTIFY _notifyHandle;
};
using SuspendHandler = SuspendHandlerWindows;
#elif defined(__linux__) && defined(HYPERION_HAS_DBUS)
class SuspendHandlerLinux : public SuspendHandlerBase {
public:
SuspendHandlerLinux();
};
using SuspendHandler = SuspendHandlerLinux;
#else
using SuspendHandler = SuspendHandlerBase;
#endif
#endif // SUSPENDHANDLER_H

View File

@ -96,6 +96,7 @@ HyperionDaemon::HyperionDaemon(const QString& rootPath, QObject* parent, bool lo
#ifdef ENABLE_CEC #ifdef ENABLE_CEC
, _cecHandler(nullptr) , _cecHandler(nullptr)
#endif #endif
, _suspendHandler(nullptr)
, _currVideoMode(VideoMode::VIDEO_2D) , _currVideoMode(VideoMode::VIDEO_2D)
{ {
HyperionDaemon::daemon = this; HyperionDaemon::daemon = this;
@ -171,6 +172,8 @@ HyperionDaemon::HyperionDaemon(const QString& rootPath, QObject* parent, bool lo
// ---- network services ----- // ---- network services -----
startNetworkServices(); startNetworkServices();
_suspendHandler = new SuspendHandler();
} }
HyperionDaemon::~HyperionDaemon() HyperionDaemon::~HyperionDaemon()
@ -337,6 +340,8 @@ void HyperionDaemon::freeObjects()
} }
#endif #endif
delete _suspendHandler;
// stop Hyperions (non blocking) // stop Hyperions (non blocking)
_instanceManager->stopAll(); _instanceManager->stopAll();

View File

@ -69,6 +69,8 @@
#include <utils/settings.h> #include <utils/settings.h>
#include <utils/Components.h> #include <utils/Components.h>
#include "SuspendHandler.h"
class HyperionIManager; class HyperionIManager;
class SysTray; class SysTray;
class JsonServer; class JsonServer;
@ -102,6 +104,11 @@ public:
/// ///
WebServer *getWebServerInstance() { return _webserver; } WebServer *getWebServerInstance() { return _webserver; }
///
/// @brief Get suspense handler pointer
///
SuspendHandler* getSuspendHandlerInstance() { return _suspendHandler; }
/// ///
/// @brief Get the current videoMode /// @brief Get the current videoMode
/// ///
@ -198,10 +205,11 @@ private:
QtWrapper* _qtGrabber; QtWrapper* _qtGrabber;
DirectXWrapper* _dxGrabber; DirectXWrapper* _dxGrabber;
SSDPHandler* _ssdp; SSDPHandler* _ssdp;
#ifdef ENABLE_CEC #ifdef ENABLE_CEC
CECHandler* _cecHandler; CECHandler* _cecHandler;
#endif #endif
SuspendHandler* _suspendHandler;
#if defined(ENABLE_FLATBUF_SERVER) #if defined(ENABLE_FLATBUF_SERVER)
FlatBufferServer* _flatBufferServer; FlatBufferServer* _flatBufferServer;
#endif #endif

View File

@ -46,6 +46,7 @@
#include "hyperiond.h" #include "hyperiond.h"
#include "systray.h" #include "systray.h"
#include "SuspendHandler.h"
using namespace commandline; using namespace commandline;
@ -54,8 +55,8 @@ using namespace commandline;
#ifndef _WIN32 #ifndef _WIN32
void signal_handler(int signum) void signal_handler(int signum)
{ {
// Hyperion Managment instance HyperionDaemon* hyperiond = HyperionDaemon::getInstance();
HyperionIManager *_hyperion = HyperionIManager::getInstance(); SuspendHandler* suspendHandler = hyperiond->getSuspendHandlerInstance();
if (signum == SIGCHLD) if (signum == SIGCHLD)
{ {
@ -64,16 +65,16 @@ void signal_handler(int signum)
} }
else if (signum == SIGUSR1) else if (signum == SIGUSR1)
{ {
if (_hyperion != nullptr) if (suspendHandler != nullptr)
{ {
_hyperion->toggleStateAllInstances(false); suspendHandler->suspend();
} }
} }
else if (signum == SIGUSR2) else if (signum == SIGUSR2)
{ {
if (_hyperion != nullptr) if (suspendHandler != nullptr)
{ {
_hyperion->toggleStateAllInstances(true); suspendHandler->resume();
} }
} }
} }

View File

@ -12,6 +12,7 @@
#include <QColor> #include <QColor>
#include <QDesktopServices> #include <QDesktopServices>
#include <QSettings> #include <QSettings>
#include <QtGlobal>
#include <utils/ColorRgb.h> #include <utils/ColorRgb.h>
#include <utils/Process.h> #include <utils/Process.h>
@ -24,6 +25,7 @@
#include "hyperiond.h" #include "hyperiond.h"
#include "systray.h" #include "systray.h"
#include "SuspendHandler.h"
SysTray::SysTray(HyperionDaemon *hyperiond) SysTray::SysTray(HyperionDaemon *hyperiond)
: QWidget() : QWidget()
@ -31,6 +33,7 @@ SysTray::SysTray(HyperionDaemon *hyperiond)
, _hyperiond(hyperiond) , _hyperiond(hyperiond)
, _hyperion(nullptr) , _hyperion(nullptr)
, _instanceManager(HyperionIManager::getInstance()) , _instanceManager(HyperionIManager::getInstance())
, _suspendHandler (hyperiond->getSuspendHandlerInstance())
, _webPort(8090) , _webPort(8090)
{ {
Q_INIT_RESOURCE(resources); Q_INIT_RESOURCE(resources);
@ -79,7 +82,15 @@ void SysTray::createTrayIcon()
restartAction = new QAction(tr("&Restart"), this); restartAction = new QAction(tr("&Restart"), this);
restartAction->setIcon(QPixmap(":/restart.svg")); restartAction->setIcon(QPixmap(":/restart.svg"));
connect(restartAction, &QAction::triggered, this , [=](){ Process::restartHyperion(11); }); connect(restartAction, &QAction::triggered, this , [=](){ Process::restartHyperion(12); });
suspendAction = new QAction(tr("&Suspend"), this);
suspendAction->setIcon(QPixmap(":/suspend.svg"));
connect(suspendAction, &QAction::triggered, _suspendHandler, QOverload<>::of(&SuspendHandler::suspend));
resumeAction = new QAction(tr("&Resume"), this);
resumeAction->setIcon(QPixmap(":/resume.svg"));
connect(resumeAction, &QAction::triggered, _suspendHandler, &SuspendHandler::resume);
colorAction = new QAction(tr("&Color"), this); colorAction = new QAction(tr("&Color"), this);
colorAction->setIcon(QPixmap(":/color.svg")); colorAction->setIcon(QPixmap(":/color.svg"));
@ -102,7 +113,7 @@ void SysTray::createTrayIcon()
_trayIconEfxMenu->setIcon(QPixmap(":/effects.svg")); _trayIconEfxMenu->setIcon(QPixmap(":/effects.svg"));
// custom effects // custom effects
for (auto efx : efxs) for (const auto &efx : efxs)
{ {
if (efx.file.mid(0, 1) != ":") if (efx.file.mid(0, 1) != ":")
{ {
@ -117,7 +128,7 @@ void SysTray::createTrayIcon()
_trayIconEfxMenu->addSeparator(); _trayIconEfxMenu->addSeparator();
// build in effects // build in effects
for (auto efx : efxs) for (const auto &efx : efxs)
{ {
if (efx.file.mid(0, 1) == ":") if (efx.file.mid(0, 1) == ":")
{ {
@ -145,7 +156,16 @@ void SysTray::createTrayIcon()
#endif #endif
_trayIconMenu->addAction(clearAction); _trayIconMenu->addAction(clearAction);
_trayIconMenu->addSeparator(); _trayIconMenu->addSeparator();
_trayIconMenu->addAction(restartAction);
_trayIconSystemMenu = new QMenu(_trayIconMenu);
_trayIconSystemMenu->setTitle(tr("Instances"));
_trayIconSystemMenu->addAction(suspendAction);
_trayIconSystemMenu->addAction(resumeAction);
_trayIconSystemMenu->addAction(restartAction);
_trayIconMenu->addMenu(_trayIconSystemMenu);
_trayIconMenu->addSeparator();
_trayIconMenu->addAction(quitAction); _trayIconMenu->addAction(quitAction);
_trayIcon = new QSystemTrayIcon(this); _trayIcon = new QSystemTrayIcon(this);
@ -180,7 +200,7 @@ void SysTray::setAutorunState()
void SysTray::setColor(const QColor & color) void SysTray::setColor(const QColor & color)
{ {
std::vector<ColorRgb> rgbColor{ ColorRgb{ (uint8_t)color.red(), (uint8_t)color.green(), (uint8_t)color.blue() } }; std::vector<ColorRgb> rgbColor{ ColorRgb{ static_cast<uint8_t>(color.red()), static_cast<uint8_t>(color.green()), static_cast<uint8_t>(color.blue()) } };
_hyperion->setColor(PriorityMuxer::FG_PRIORITY,rgbColor, PriorityMuxer::ENDLESS); _hyperion->setColor(PriorityMuxer::FG_PRIORITY,rgbColor, PriorityMuxer::ENDLESS);
} }

View File

@ -12,6 +12,7 @@
#include <hyperion/Hyperion.h> #include <hyperion/Hyperion.h>
#include <hyperion/HyperionIManager.h> #include <hyperion/HyperionIManager.h>
#include "SuspendHandler.h"
class HyperionDaemon; class HyperionDaemon;
@ -61,6 +62,8 @@ private:
QAction *quitAction; QAction *quitAction;
QAction *restartAction; QAction *restartAction;
QAction *suspendAction;
QAction *resumeAction;
QAction *startAction; QAction *startAction;
QAction *stopAction; QAction *stopAction;
QAction *colorAction; QAction *colorAction;
@ -75,9 +78,12 @@ private:
#if defined(ENABLE_EFFECTENGINE) #if defined(ENABLE_EFFECTENGINE)
QMenu *_trayIconEfxMenu; QMenu *_trayIconEfxMenu;
#endif #endif
QMenu *_trayIconSystemMenu;
QColorDialog _colorDlg; QColorDialog _colorDlg;
HyperionDaemon *_hyperiond; HyperionDaemon *_hyperiond;
Hyperion *_hyperion; Hyperion *_hyperion;
HyperionIManager *_instanceManager; HyperionIManager *_instanceManager;
quint16 _webPort; quint16 _webPort;
SuspendHandler *_suspendHandler;
}; };