hyperiond desktop integration (#453)

* add deployment

* add correct api key

* fix cmake lists and add heroku app name

* Update .gitmodules

sync modules with upstream

* add possibility to start hyperiond as systray app

* cleanup

* - new command line options: --desktop --service to set desired mode (systray icon / console only)
- auto detect x server - if avail run in gui mode
- on osx always run in gui mode
- use existing icon from webconfig, instead of own icon
- add ability to no gice a config file name. If config not given, default config file will be set (home dir, or hyperiond dir, depending on writable state)

* fix warnings and compile error

* use own icon for systray purpose

* use new logo

* - set application properties
- fix force service mode
This commit is contained in:
redPanther 2017-08-01 17:29:47 +02:00 committed by GitHub
parent 5c7085439b
commit 6625a318ac
12 changed files with 307 additions and 28 deletions

View File

@ -147,8 +147,8 @@ private:
double _y_frac_min;
double _x_frac_max;
double _y_frac_max;
int _currentFrame;
int _currentFrame;
QSocketNotifier * _streamNotifier;
ImageResampler _imageResampler;

View File

@ -5,6 +5,7 @@
#include <cstdint>
#include <cstring>
#include <algorithm>
#include <cassert>
#include <utils/ColorRgb.h>

View File

@ -18,6 +18,8 @@ public:
void start();
void stop();
quint16 getPort() { return _port; };
private:
Hyperion* _hyperion;
QString _baseUrl;

View File

@ -49,12 +49,12 @@ V4L2Grabber::V4L2Grabber(const QString & device
, _noSignalThresholdColor(ColorRgb{0,0,0})
, _signalDetectionEnabled(true)
, _noSignalDetected(false)
, _noSignalCounter(0)
, _x_frac_min(0.25)
, _y_frac_min(0.25)
, _x_frac_max(0.75)
, _y_frac_max(0.75)
, _currentFrame(0)
, _noSignalCounter(0)
, _streamNotifier(nullptr)
, _imageResampler()
, _log(Logger::getInstance("V4L2:"+device))

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -1,5 +1,6 @@
<RCC>
<qresource prefix="/">
<file alias="hyperion-icon.png">hyperion-icon_32px.png</file>
<file alias="hyperion-schema">hyperion.schema.json</file>
<file alias="hyperion_default.config">../../config/hyperion.config.json.default</file>
<file alias="schema-general.json">schema/schema-general.json</file>

View File

@ -1,7 +1,9 @@
add_executable(hyperiond
hyperiond.h
systray.h
hyperiond.cpp
systray.cpp
main.cpp
)
@ -42,6 +44,8 @@ if (ENABLE_X11)
target_link_libraries(hyperiond x11-grabber )
endif ()
qt5_use_modules(hyperiond Core Gui Network Widgets)
install ( TARGETS hyperiond DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" )
install ( DIRECTORY ${CMAKE_SOURCE_DIR}/bin/service DESTINATION "share/hyperion/" COMPONENT "${PLATFORM}" )
install ( FILES ${CMAKE_SOURCE_DIR}/effects/readme.txt DESTINATION "share/hyperion/effects" COMPONENT "${PLATFORM}" )

View File

@ -137,7 +137,7 @@ void HyperionDaemon::run()
void HyperionDaemon::loadConfig(const QString & configFile)
{
Info(_log, "Selected configuration file: %s", configFile.toUtf8().constData());
Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile));
// make sure the resources are loaded (they may be left out after static linking)
Q_INIT_RESOURCE(resource);
@ -224,7 +224,7 @@ void HyperionDaemon::startInitialEffect()
else
{
int result = hyperion->setEffect(fgEffectConfig, FG_PRIORITY, fg_duration_ms);
Info(_log,"Inital foreground effect '%s' %s", fgEffectConfig.toUtf8().constData(), ((result == 0) ? "started" : "failed"));
Info(_log,"Inital foreground effect '%s' %s", QSTRING_CSTR(fgEffectConfig), ((result == 0) ? "started" : "failed"));
}
}
// initial background effect/color
@ -246,7 +246,7 @@ void HyperionDaemon::startInitialEffect()
else
{
int result = hyperion->setEffect(bgEffectConfig, BG_PRIORITY, DURATION_INFINITY);
Info(_log,"Inital background effect '%s' %s", bgEffectConfig.toUtf8().constData(), ((result == 0) ? "started" : "failed"));
Info(_log,"Inital background effect '%s' %s", QSTRING_CSTR(bgEffectConfig), ((result == 0) ? "started" : "failed"));
}
}
@ -435,7 +435,7 @@ void HyperionDaemon::createSystemFrameGrabber()
{
type = "framebuffer";
}
Info( _log, "set screen capture device to '%s'", type.toUtf8().constData());
Info( _log, "set screen capture device to '%s'", QSTRING_CSTR(type));
}
bool grabberCompState = grabberConfig["enable"].toBool(true);
@ -445,7 +445,7 @@ void HyperionDaemon::createSystemFrameGrabber()
else if (type == "amlogic") { createGrabberAmlogic(); createGrabberFramebuffer(grabberConfig); }
else if (type == "osx") createGrabberOsx(grabberConfig);
else if (type == "x11") createGrabberX11(grabberConfig);
else { Warning( _log, "unknown framegrabber type '%s'", type.toUtf8().constData()); grabberCompState = false; }
else { Warning( _log, "unknown framegrabber type '%s'", QSTRING_CSTR(type)); grabberCompState = false; }
// _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_GRABBER, grabberCompState);
_hyperion->setComponentState(hyperion::COMP_GRABBER, grabberCompState );

View File

@ -48,9 +48,14 @@
#include <utils/Stats.h>
#include <QJsonObject>
class SysTray;
class HyperionDaemon : public QObject
{
Q_OBJECT
friend SysTray;
public:
HyperionDaemon(QString configFile, QObject *parent=nullptr);
~HyperionDaemon();

View File

@ -11,12 +11,14 @@
#include <exception>
#include <QCoreApplication>
#include <QApplication>
#include <QLocale>
#include <QFile>
#include <QString>
#include <QResource>
#include <QDir>
#include <QStringList>
#include <QSystemTrayIcon>
#include "HyperionConfig.h"
@ -26,7 +28,12 @@
#include <commandline/Parser.h>
#include <commandline/IntOption.h>
#ifdef ENABLE_X11
#include <X11/Xlib.h>
#endif
#include "hyperiond.h"
#include "systray.h"
using namespace commandline;
@ -58,6 +65,54 @@ void startNewHyperion(int parentPid, std::string hyperionFile, std::string confi
}
}
QCoreApplication* createApplication(int &argc, char *argv[])
{
bool isGuiApp = false;
bool forceNoGui = false;
// command line
for (int i = 1; i < argc; ++i)
{
if (qstrcmp(argv[i], "--desktop") == 0)
{
isGuiApp = true;
}
else if (qstrcmp(argv[i], "--service") == 0)
{
isGuiApp = false;
forceNoGui = true;
}
}
// on osx/windows gui always available
#if defined(__APPLE__) || defined(__WIN32__)
isGuiApp = true && ! forceNoGui;
#else
if (!forceNoGui)
{
// if x11, then test if xserver is available
#ifdef ENABLE_X11
Display* dpy = XOpenDisplay(NULL);
if (dpy != NULL)
{
XCloseDisplay(dpy);
isGuiApp = true;
}
}
#endif
#endif
if (isGuiApp)
{
QApplication* app = new QApplication(argc, argv);
app->setApplicationDisplayName("Hyperion");
return app;
}
QCoreApplication* app = new QCoreApplication(argc, argv);
app->setApplicationName("Hyperion");
app->setApplicationVersion(HYPERION_VERSION);
return app;
}
int main(int argc, char** argv)
{
@ -68,7 +123,8 @@ int main(int argc, char** argv)
Logger::setLogLevel(Logger::WARNING);
// Initialising QCoreApplication
QCoreApplication app(argc, argv);
QScopedPointer<QCoreApplication> app(createApplication(argc, argv));
bool isGuiApp = (qobject_cast<QApplication *>(app.data()) != 0 && QSystemTrayIcon::isSystemTrayAvailable());
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
@ -88,14 +144,16 @@ int main(int argc, char** argv)
BooleanOption & silentOption = parser.add<BooleanOption>('s', "silent", "do not print any outputs");
BooleanOption & verboseOption = parser.add<BooleanOption>('v', "verbose", "Increase verbosity");
BooleanOption & debugOption = parser.add<BooleanOption>('d', "debug", "Show debug messages");
parser.add<BooleanOption>(0x0, "desktop", "show systray on desktop");
parser.add<BooleanOption>(0x0, "service", "force hyperion to start as console service");
Option & exportConfigOption = parser.add<Option> (0x0, "export-config", "export default config to file");
Option & exportEfxOption = parser.add<Option> (0x0, "export-effects", "export effects to given path");
parser.addPositionalArgument("config-files", QCoreApplication::translate("main", "Configuration file"), "config.file");
parser.process(app);
parser.process(*qApp);
const QStringList configFiles = parser.positionalArguments();
QStringList configFiles = parser.positionalArguments();
int logLevelCheck = 0;
if (parser.isSet(silentOption))
@ -169,23 +227,39 @@ int main(int argc, char** argv)
return 1;
}
// handle default config file
if (configFiles.size() == 0)
{
QString hyperiond_path = QDir::homePath();
QString hyperiond_config = hyperiond_path+"/.hyperion.config.json";
QFileInfo hyperiond_pathinfo(hyperiond_path);
if ( ! hyperiond_pathinfo.isWritable() && ! QFile::exists(hyperiond_config) )
{
QFileInfo hyperiond_fileinfo(argv[0]);
hyperiond_config = hyperiond_fileinfo.absolutePath()+"/hyperion.config.json";
}
configFiles.append(hyperiond_config);
Info(log, "No config file given. Standard config file used: %s", QSTRING_CSTR(configFiles[0]));
}
bool exportDefaultConfig = false;
bool exitAfterexportDefaultConfig = false;
bool exitAfterExportDefaultConfig = false;
QString exportConfigFileTarget;
if (parser.isSet(exportConfigOption))
{
exportDefaultConfig = true;
exitAfterexportDefaultConfig = true;
exitAfterExportDefaultConfig = true;
exportConfigFileTarget = exportConfigOption.value(parser);
}
else if ( configFiles.size() > 0 && ! QFile::exists(configFiles[0]) )
else if ( ! QFile::exists(configFiles[0]) )
{
exportDefaultConfig = true;
exportConfigFileTarget = configFiles[0];
Warning(log, "Your configuration file does not exist. hyperion writes default config");
}
if (exportDefaultConfig)
{
Q_INIT_RESOURCE(resource);
@ -194,21 +268,15 @@ int main(int argc, char** argv)
{
QFile::setPermissions(exportConfigFileTarget, PERM0664 );
Info(log, "export complete.");
if (exitAfterexportDefaultConfig) return 0;
if (exitAfterExportDefaultConfig) return 0;
}
else
{
Error(log, "error while export to %s",exportConfigFileTarget.toLocal8Bit().constData());
if (exitAfterexportDefaultConfig) return 1;
Error(log, "error while export to %s", QSTRING_CSTR(exportConfigFileTarget) );
return 1;
}
}
if (configFiles.size() == 0)
{
Error(log, "Missing required configuration file. Usage: hyperiond <options ...> config.file");
parser.showHelp(0);
return 1;
}
if (configFiles.size() > 1)
{
Warning(log, "You provided more than one config file. Hyperion will use only the first one");
@ -223,10 +291,11 @@ int main(int argc, char** argv)
#endif
}
HyperionDaemon* hyperiond = nullptr;
try
{
hyperiond = new HyperionDaemon(configFiles[0], &app);
hyperiond = new HyperionDaemon(configFiles[0], qApp);
hyperiond->run();
}
catch (std::exception& e)
@ -238,10 +307,21 @@ int main(int argc, char** argv)
WebConfig* webConfig = nullptr;
try
{
webConfig = new WebConfig(&app);
webConfig = new WebConfig(qApp);
// run the application
rc = app.exec();
Info(log, "INFO: Application closed with code %d", rc);
if (isGuiApp)
{
Info(log, "start systray");
QApplication::setQuitOnLastWindowClosed(false);
SysTray tray(hyperiond, webConfig->getPort());
tray.hide();
rc = (qobject_cast<QApplication *>(app.data()))->exec();
}
else
{
rc = app->exec();
}
Info(log, "Application closed with code %d", rc);
}
catch (std::exception& e)
{

136
src/hyperiond/systray.cpp Normal file
View File

@ -0,0 +1,136 @@
#include <list>
#include <QPixmap>
#include <QWindow>
#include <QGuiApplication>
#include <QWidget>
#include <QColor>
#include <QDesktopServices>
#include <utils/ColorRgb.h>
#include <effectengine/EffectDefinition.h>
#include "hyperiond.h"
#include "systray.h"
SysTray::SysTray(HyperionDaemon *hyperiond, quint16 webPort)
: QWidget()
, _colorDlg(this)
, _hyperiond(hyperiond)
, _webPort(webPort)
{
Q_INIT_RESOURCE(resource);
_hyperion = Hyperion::getInstance();
createTrayIcon();
connect(_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
connect(&_colorDlg, SIGNAL(currentColorChanged(const QColor&)), this, SLOT(setColor(const QColor &)));
QIcon icon(":/hyperion-icon.png");
_trayIcon->setIcon(icon);
_trayIcon->show();
setWindowIcon(icon);
_colorDlg.setModal(true);
_colorDlg.setOptions(QColorDialog::NoButtons);
}
SysTray::~SysTray()
{
}
void SysTray::iconActivated(QSystemTrayIcon::ActivationReason reason)
{
switch (reason)
{
case QSystemTrayIcon::Trigger:
break;
case QSystemTrayIcon::DoubleClick:
showColorDialog();
break;
case QSystemTrayIcon::MiddleClick:
break;
default: ;
}
}
void SysTray::createTrayIcon()
{
quitAction = new QAction(tr("&Quit"), this);
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
colorAction = new QAction(tr("&Color"), this);
connect(colorAction, SIGNAL(triggered()), this, SLOT(showColorDialog()));
settingsAction = new QAction(tr("&Settings"), this);
connect(settingsAction, SIGNAL(triggered()), this, SLOT(settings()));
clearAction = new QAction(tr("&Clear"), this);
connect(clearAction, SIGNAL(triggered()), this, SLOT(clearEfxColor()));
const std::list<EffectDefinition> efxs = _hyperion->getEffects();
_trayIconMenu = new QMenu(this);
_trayIconEfxMenu = new QMenu(_trayIconMenu);
_trayIconEfxMenu->setTitle(tr("Effects"));
for (auto efx : efxs)
{
QAction *efxAction = new QAction(efx.name, this);
connect(efxAction, SIGNAL(triggered()), this, SLOT(setEffect()));
_trayIconEfxMenu->addAction(efxAction);
}
_trayIconMenu->addAction(settingsAction);
_trayIconMenu->addSeparator();
_trayIconMenu->addAction(colorAction);
_trayIconMenu->addMenu(_trayIconEfxMenu);
_trayIconMenu->addAction(clearAction);
_trayIconMenu->addSeparator();
_trayIconMenu->addAction(quitAction);
_trayIcon = new QSystemTrayIcon(this);
_trayIcon->setContextMenu(_trayIconMenu);
}
void SysTray::setColor(const QColor & color)
{
ColorRgb rgbColor;
rgbColor.red = color.red();
rgbColor.green = color.green();
rgbColor.blue =color.blue();
_hyperion->setColor(1 ,rgbColor, 0);
}
void SysTray::showColorDialog()
{
if(_colorDlg.isVisible())
{
_colorDlg.hide();
}
else
{
_colorDlg.show();
}
}
void SysTray::closeEvent(QCloseEvent *event)
{
event->ignore();
}
void SysTray::settings()
{
QDesktopServices::openUrl(QUrl("http://localhost:"+QString::number(_webPort)+"/", QUrl::TolerantMode));
}
void SysTray::setEffect()
{
QString efxName = qobject_cast<QAction*>(sender())->text();
_hyperion->setEffect(efxName, 1);
}
void SysTray::clearEfxColor()
{
_hyperion->clear(1);
}

50
src/hyperiond/systray.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include <QSystemTrayIcon>
#include <QMenu>
#include <QWidget>
#include <QColorDialog>
#include <QCloseEvent>
#include <hyperion/Hyperion.h>
class HyperionDaemon;
class SysTray : public QWidget
{
Q_OBJECT
public:
SysTray(HyperionDaemon *hyperiond, quint16 webPort);
~SysTray();
public slots:
void showColorDialog();
void setColor(const QColor & color);
void closeEvent(QCloseEvent *event);
void settings();
void setEffect();
void clearEfxColor();
private slots:
void iconActivated(QSystemTrayIcon::ActivationReason reason);
private:
void createTrayIcon();
QAction *quitAction;
QAction *startAction;
QAction *stopAction;
QAction *colorAction;
QAction *settingsAction;
QAction *clearAction;
QSystemTrayIcon *_trayIcon;
QMenu *_trayIconMenu;
QMenu *_trayIconEfxMenu;
QColorDialog _colorDlg;
HyperionDaemon *_hyperiond;
quint16 _webPort;
Hyperion *_hyperion;
};