2016-06-17 01:25:40 +02:00
# include <cassert>
# include <csignal>
# include <unistd.h>
2017-03-21 17:55:46 +01:00
# include <stdlib.h>
2016-08-07 18:39:45 +02:00
# ifndef __APPLE__
/* prctl is Linux only */
# include <sys/prctl.h>
# endif
2016-07-10 12:18:40 +02:00
# include <exception>
2016-06-17 01:25:40 +02:00
# include <QCoreApplication>
2017-08-01 17:29:47 +02:00
# include <QApplication>
2016-06-17 01:25:40 +02:00
# include <QLocale>
# include <QFile>
2016-08-06 08:28:42 +02:00
# include <QString>
2016-09-17 00:40:29 +02:00
# include <QResource>
# include <QDir>
# include <QStringList>
2017-08-01 17:29:47 +02:00
# include <QSystemTrayIcon>
2016-06-17 01:25:40 +02:00
# include "HyperionConfig.h"
# include <utils/Logger.h>
2016-10-13 21:59:10 +02:00
# include <utils/FileUtils.h>
2016-08-28 15:10:43 +02:00
# include <commandline/Parser.h>
# include <commandline/IntOption.h>
2016-06-17 01:25:40 +02:00
2017-08-01 17:29:47 +02:00
# ifdef ENABLE_X11
# include <X11/Xlib.h>
# endif
2016-06-17 01:25:40 +02:00
# include "hyperiond.h"
2017-08-01 17:29:47 +02:00
# include "systray.h"
2016-06-17 01:25:40 +02:00
2016-08-28 15:10:43 +02:00
using namespace commandline ;
2016-06-17 01:25:40 +02:00
2016-12-03 21:11:52 +01:00
# define PERM0664 QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther | QFileDevice::WriteOwner | QFileDevice::WriteGroup
2016-06-17 01:25:40 +02:00
void signal_handler ( const int signum )
{
2017-01-29 21:20:12 +01:00
if ( signum = = SIGCHLD )
{
// only quit when a registered child process is gone
// currently this feature is not active ...
return ;
}
2016-06-17 01:25:40 +02:00
QCoreApplication : : quit ( ) ;
// reset signal handler to default (in case this handler is not capable of stopping)
signal ( signum , SIG_DFL ) ;
}
void startNewHyperion ( int parentPid , std : : string hyperionFile , std : : string configFile )
{
2017-01-29 21:20:12 +01:00
pid_t childPid = fork ( ) ; // child pid should store elsewhere for later use
if ( childPid = = 0 )
2016-06-17 01:25:40 +02:00
{
sleep ( 3 ) ;
execl ( hyperionFile . c_str ( ) , hyperionFile . c_str ( ) , " --parent " , QString : : number ( parentPid ) . toStdString ( ) . c_str ( ) , configFile . c_str ( ) , NULL ) ;
exit ( 0 ) ;
}
}
2017-08-01 17:29:47 +02:00
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 ) ;
2017-10-12 11:55:03 +02:00
if ( dpy ! = NULL )
2017-08-01 17:29:47 +02:00
{
XCloseDisplay ( dpy ) ;
isGuiApp = true ;
}
2017-08-04 19:44:52 +02:00
# endif
2017-08-01 17:29:47 +02:00
}
# 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 ;
}
2016-06-17 01:25:40 +02:00
int main ( int argc , char * * argv )
{
2017-03-21 17:55:46 +01:00
setenv ( " AVAHI_COMPAT_NOWARN " , " 1 " , 1 ) ;
2016-06-21 21:41:26 +02:00
// initialize main logger and set global log level
Logger * log = Logger : : getInstance ( " MAIN " ) ;
2016-06-23 13:48:49 +02:00
Logger : : setLogLevel ( Logger : : WARNING ) ;
2016-06-17 01:25:40 +02:00
// Initialising QCoreApplication
2017-08-01 17:29:47 +02:00
QScopedPointer < QCoreApplication > app ( createApplication ( argc , argv ) ) ;
bool isGuiApp = ( qobject_cast < QApplication * > ( app . data ( ) ) ! = 0 & & QSystemTrayIcon : : isSystemTrayAvailable ( ) ) ;
2016-06-17 01:25:40 +02:00
signal ( SIGINT , signal_handler ) ;
signal ( SIGTERM , signal_handler ) ;
2017-01-29 21:20:12 +01:00
signal ( SIGABRT , signal_handler ) ;
2016-06-17 01:25:40 +02:00
signal ( SIGCHLD , signal_handler ) ;
2016-07-18 09:23:55 +02:00
signal ( SIGPIPE , signal_handler ) ;
2016-06-17 01:25:40 +02:00
// force the locale
setlocale ( LC_ALL , " C " ) ;
QLocale : : setDefault ( QLocale : : c ( ) ) ;
2016-08-28 15:10:43 +02:00
Parser parser ( " Hyperion Daemon " ) ;
parser . addHelpOption ( ) ;
2016-06-17 01:25:40 +02:00
2016-09-17 00:40:29 +02:00
BooleanOption & versionOption = parser . add < BooleanOption > ( 0x0 , " version " , " Show version information " ) ;
2017-10-12 11:55:03 +02:00
Option & rootPathOption = parser . add < Option > ( 0x0 , " rootPath " , " Overwrite root path for all hyperion user files, defaults to home directory of current user " ) ;
2016-09-17 00:40:29 +02:00
IntOption & parentOption = parser . add < IntOption > ( ' p ' , " parent " , " pid of parent hyperiond " ) ; // 2^22 is the max for Linux
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 " ) ;
2017-08-01 17:29:47 +02:00
parser . add < BooleanOption > ( 0x0 , " desktop " , " show systray on desktop " ) ;
parser . add < BooleanOption > ( 0x0 , " service " , " force hyperion to start as console service " ) ;
2016-09-17 00:40:29 +02:00
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 " ) ;
2016-11-20 22:57:19 +01:00
parser . addPositionalArgument ( " config-files " , QCoreApplication : : translate ( " main " , " Configuration file " ) , " config.file " ) ;
2016-06-17 01:25:40 +02:00
2017-08-01 17:29:47 +02:00
parser . process ( * qApp ) ;
2016-08-28 15:10:43 +02:00
2017-08-01 17:29:47 +02:00
QStringList configFiles = parser . positionalArguments ( ) ;
2016-06-17 01:25:40 +02:00
2016-06-23 13:48:49 +02:00
int logLevelCheck = 0 ;
2016-08-28 15:10:43 +02:00
if ( parser . isSet ( silentOption ) )
2016-06-23 13:48:49 +02:00
{
Logger : : setLogLevel ( Logger : : OFF ) ;
logLevelCheck + + ;
}
2016-08-28 15:10:43 +02:00
if ( parser . isSet ( verboseOption ) )
2016-06-23 13:48:49 +02:00
{
Logger : : setLogLevel ( Logger : : INFO ) ;
logLevelCheck + + ;
}
2016-08-28 15:10:43 +02:00
if ( parser . isSet ( debugOption ) )
2016-06-23 13:48:49 +02:00
{
Logger : : setLogLevel ( Logger : : DEBUG ) ;
logLevelCheck + + ;
}
if ( logLevelCheck > 1 )
{
Error ( log , " aborting, because options --silent --verbose --debug can't used together " ) ;
return 0 ;
}
2016-06-17 01:25:40 +02:00
2016-08-28 15:10:43 +02:00
if ( parser . isSet ( versionOption ) )
2016-06-17 01:25:40 +02:00
{
std : : cout
< < " Hyperion Ambilight Deamon ( " < < getpid ( ) < < " ) " < < std : : endl
2016-06-24 23:22:31 +02:00
< < " \t Version : " < < HYPERION_VERSION < < " ( " < < HYPERION_BUILD_ID < < " ) " < < std : : endl
2016-06-17 01:25:40 +02:00
< < " \t Build Time: " < < __DATE__ < < " " < < __TIME__ < < std : : endl ;
return 0 ;
}
2016-09-17 00:40:29 +02:00
if ( parser . isSet ( exportEfxOption ) )
{
Q_INIT_RESOURCE ( EffectEngine ) ;
QDir directory ( " :/effects/ " ) ;
QDir destDir ( exportEfxOption . value ( parser ) ) ;
if ( directory . exists ( ) & & destDir . exists ( ) )
{
std : : cout < < " extract to folder: " < < std : : endl ;
2016-10-13 22:58:16 +02:00
QStringList filenames = directory . entryList ( QStringList ( ) < < " * " , QDir : : Files , QDir : : Name | QDir : : IgnoreCase ) ;
2016-12-03 21:11:52 +01:00
QString destFileName ;
2016-09-17 00:40:29 +02:00
foreach ( const QString & filename , filenames )
{
2016-12-03 21:11:52 +01:00
destFileName = destDir . dirName ( ) + " / " + filename ;
if ( QFile : : exists ( destFileName ) )
2016-09-17 00:40:29 +02:00
{
2016-12-03 21:11:52 +01:00
QFile : : remove ( destFileName ) ;
2016-09-17 00:40:29 +02:00
}
2017-10-12 11:55:03 +02:00
2016-09-17 00:40:29 +02:00
std : : cout < < " Extract: " < < filename . toStdString ( ) < < " ... " ;
2016-12-03 21:11:52 +01:00
if ( QFile : : copy ( QString ( " :/effects/ " ) + filename , destFileName ) )
2016-09-17 00:40:29 +02:00
{
2016-12-03 21:11:52 +01:00
QFile : : setPermissions ( destFileName , PERM0664 ) ;
2016-09-17 00:40:29 +02:00
std : : cout < < " ok " < < std : : endl ;
}
else
{
std : : cout < < " error, aborting " < < std : : endl ;
return 1 ;
}
}
return 0 ;
}
Error ( log , " can not export to %s " , exportEfxOption . getCString ( parser ) ) ;
return 1 ;
}
2016-10-13 21:59:10 +02:00
2017-10-12 11:55:03 +02:00
// handle rootPath for user data, default path is home directory + /.hyperion
QString rootPath = QDir : : homePath ( ) + " /.hyperion " ;
if ( parser . isSet ( rootPathOption ) )
2017-08-01 17:29:47 +02:00
{
2017-10-12 11:55:03 +02:00
QDir rDir ( rootPathOption . value ( parser ) ) ;
if ( ! rDir . mkpath ( rootPathOption . value ( parser ) ) )
2017-08-01 17:29:47 +02:00
{
2017-10-12 11:55:03 +02:00
Error ( log , " Can't create root path '%s', falling back to home directory " , QSTRING_CSTR ( rDir . absolutePath ( ) ) ) ;
2017-08-01 17:29:47 +02:00
}
2017-10-12 11:55:03 +02:00
else
{
rootPath = rDir . absolutePath ( ) ;
}
}
// create /.hyperion folder for default path, check if the directory is read/writeable
// Note: No further checks inside Hyperion. FileUtils::writeFile() will resolve permission errors and others that occur during runtime
QDir mDir ( rootPath ) ;
QFileInfo mFi ( rootPath ) ;
if ( ! mDir . mkpath ( rootPath ) | | ! mFi . isWritable ( ) | | ! mDir . isReadable ( ) )
{
throw std : : runtime_error ( " The specified root path can't be created or isn't read/writeable. Please setup the permissions correctly! " ) ;
}
2017-08-01 17:29:47 +02:00
2017-10-12 11:55:03 +02:00
// determine name of config file, defaults to hyperion_main.json
// create config folder
QString cPath ( rootPath + " /config " ) ;
QDir ( ) . mkpath ( rootPath + " /config " ) ;
if ( configFiles . size ( ) > 0 )
{
// use argument config file
// check if file has a path and ends with .json
if ( configFiles [ 0 ] . contains ( " / " ) )
throw std : : runtime_error ( " Don't provide a path to config file, just a config name is allowed! " ) ;
if ( ! configFiles [ 0 ] . endsWith ( " .json " ) )
configFiles [ 0 ] . append ( " .json " ) ;
configFiles . prepend ( cPath + " / " + configFiles [ 0 ] ) ;
}
else
{
// use default config file
configFiles . append ( cPath + " /hyperion_main.json " ) ;
2017-08-01 17:29:47 +02:00
}
2016-10-13 21:59:10 +02:00
bool exportDefaultConfig = false ;
2017-08-01 17:29:47 +02:00
bool exitAfterExportDefaultConfig = false ;
2016-10-13 21:59:10 +02:00
QString exportConfigFileTarget ;
if ( parser . isSet ( exportConfigOption ) )
{
exportDefaultConfig = true ;
2017-08-01 17:29:47 +02:00
exitAfterExportDefaultConfig = true ;
2016-10-13 21:59:10 +02:00
exportConfigFileTarget = exportConfigOption . value ( parser ) ;
}
2017-08-01 17:29:47 +02:00
else if ( ! QFile : : exists ( configFiles [ 0 ] ) )
2016-10-13 21:59:10 +02:00
{
exportDefaultConfig = true ;
exportConfigFileTarget = configFiles [ 0 ] ;
2017-10-12 11:55:03 +02:00
Warning ( log , " Create new config file (%s) " , QSTRING_CSTR ( configFiles [ 0 ] ) ) ;
2016-10-13 21:59:10 +02:00
}
2017-08-01 17:29:47 +02:00
2016-10-13 21:59:10 +02:00
if ( exportDefaultConfig )
{
Q_INIT_RESOURCE ( resource ) ;
QDir ( ) . mkpath ( FileUtils : : getDirName ( exportConfigFileTarget ) ) ;
if ( QFile : : copy ( " :/hyperion_default.config " , exportConfigFileTarget ) )
{
2016-12-03 21:11:52 +01:00
QFile : : setPermissions ( exportConfigFileTarget , PERM0664 ) ;
2016-10-13 21:59:10 +02:00
Info ( log , " export complete. " ) ;
2017-08-01 17:29:47 +02:00
if ( exitAfterExportDefaultConfig ) return 0 ;
2016-10-13 21:59:10 +02:00
}
2016-12-03 21:11:52 +01:00
else
{
2017-08-01 17:29:47 +02:00
Error ( log , " error while export to %s " , QSTRING_CSTR ( exportConfigFileTarget ) ) ;
return 1 ;
2016-12-03 21:11:52 +01:00
}
2016-10-13 21:59:10 +02:00
}
2016-08-28 15:10:43 +02:00
int parentPid = parser . value ( parentOption ) . toInt ( ) ;
if ( parentPid > 0 )
2016-06-17 01:25:40 +02:00
{
2016-08-28 15:10:43 +02:00
Info ( log , " hyperiond client, parent is pid %d " , parentPid ) ;
2016-08-07 18:39:45 +02:00
# ifndef __APPLE__
2016-06-17 01:25:40 +02:00
prctl ( PR_SET_PDEATHSIG , SIGHUP ) ;
2016-08-07 18:39:45 +02:00
# endif
2016-06-17 01:25:40 +02:00
}
2016-08-28 15:10:43 +02:00
2017-08-01 17:29:47 +02:00
2016-06-20 23:41:07 +02:00
HyperionDaemon * hyperiond = nullptr ;
try
{
2018-12-28 18:12:45 +01:00
hyperiond = new HyperionDaemon ( configFiles [ 0 ] , rootPath , qApp , bool ( logLevelCheck ) ) ;
2016-06-20 23:41:07 +02:00
}
2016-07-10 12:18:40 +02:00
catch ( std : : exception & e )
2016-06-20 23:41:07 +02:00
{
2016-07-10 12:18:40 +02:00
Error ( log , " Hyperion Daemon aborted: \n %s " , e . what ( ) ) ;
2016-06-20 23:41:07 +02:00
}
2016-06-17 01:25:40 +02:00
2016-07-10 12:18:40 +02:00
int rc = 1 ;
try
{
// run the application
2017-08-01 17:29:47 +02:00
if ( isGuiApp )
{
Info ( log , " start systray " ) ;
QApplication : : setQuitOnLastWindowClosed ( false ) ;
2018-12-30 22:07:53 +01:00
SysTray tray ( hyperiond ) ;
2017-08-01 17:29:47 +02:00
tray . hide ( ) ;
rc = ( qobject_cast < QApplication * > ( app . data ( ) ) ) - > exec ( ) ;
}
else
{
rc = app - > exec ( ) ;
}
Info ( log , " Application closed with code %d " , rc ) ;
2016-07-10 12:18:40 +02:00
}
catch ( std : : exception & e )
{
Error ( log , " Hyperion aborted: \n %s " , e . what ( ) ) ;
}
2016-06-17 01:25:40 +02:00
2016-06-21 21:41:26 +02:00
// delete components
2016-06-19 00:56:47 +02:00
delete hyperiond ;
2016-06-21 21:41:26 +02:00
Logger : : deleteInstance ( ) ;
2016-06-17 01:25:40 +02:00
return rc ;
}