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>
2019-01-07 18:13:49 +01:00
# include <stdio.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>
2019-07-14 22:43:22 +02:00
# include <QProcess>
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
2019-07-14 22:43:22 +02:00
unsigned int getProcessIdsByProcessName ( const char * processName , QStringList & listOfPids )
{
// Clear content of returned list of PIDS
listOfPids . clear ( ) ;
# if defined(WIN32)
// Get the list of process identifiers.
DWORD aProcesses [ 1024 ] , cbNeeded , cProcesses ;
unsigned int i ;
if ( ! EnumProcesses ( aProcesses , sizeof ( aProcesses ) , & cbNeeded ) )
return 0 ;
// Calculate how many process identifiers were returned.
cProcesses = cbNeeded / sizeof ( DWORD ) ;
// Search for a matching name for each process
for ( i = 0 ; i < cProcesses ; i + + )
{
if ( aProcesses [ i ] ! = 0 )
{
char szProcessName [ MAX_PATH ] = { 0 } ;
DWORD processID = aProcesses [ i ] ;
// Get a handle to the process.
HANDLE hProcess = OpenProcess ( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ , FALSE , processID ) ;
// Get the process name
if ( NULL ! = hProcess )
{
HMODULE hMod ;
DWORD cbNeeded ;
if ( EnumProcessModules ( hProcess , & hMod , sizeof ( hMod ) , & cbNeeded ) )
GetModuleBaseNameA ( hProcess , hMod , szProcessName , sizeof ( szProcessName ) / sizeof ( char ) ) ;
// Release the handle to the process.
CloseHandle ( hProcess ) ;
if ( * szProcessName ! = 0 & & strcmp ( processName , szProcessName ) = = 0 )
listOfPids . append ( QString : : number ( processID ) ) ;
}
}
}
return listOfPids . count ( ) ;
# else
// Run pgrep, which looks through the currently running processses and lists the process IDs
// which match the selection criteria to stdout.
QProcess process ;
process . start ( " pgrep " , QStringList ( ) < < processName ) ;
process . waitForReadyRead ( ) ;
QByteArray bytes = process . readAllStandardOutput ( ) ;
process . terminate ( ) ;
process . waitForFinished ( ) ;
process . kill ( ) ;
// Output is something like "2472\n2323" for multiple instances
if ( bytes . isEmpty ( ) )
return 0 ;
listOfPids = QString ( bytes ) . split ( " \n " , QString : : SkipEmptyParts ) ;
return listOfPids . count ( ) ;
# endif
}
2016-06-17 01:25:40 +02:00
void signal_handler ( const int signum )
{
2019-07-14 22:43:22 +02:00
// SIGUSR1 and SIGUSR2 must be rewritten
// Hyperion Managment instance
HyperionIManager * _hyperion = HyperionIManager : : getInstance ( ) ;
2019-04-19 08:22:23 +02:00
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 ;
}
2019-04-19 08:22:23 +02:00
else if ( signum = = SIGUSR1 )
{
if ( _hyperion ! = nullptr )
{
2019-07-14 22:43:22 +02:00
// _hyperion->setComponentState(hyperion::COMP_SMOOTHING, false);
// _hyperion->setComponentState(hyperion::COMP_LEDDEVICE, false);
2019-04-19 08:22:23 +02:00
}
return ;
}
else if ( signum = = SIGUSR2 )
{
if ( _hyperion ! = nullptr )
{
2019-07-14 22:43:22 +02:00
// _hyperion->setComponentState(hyperion::COMP_LEDDEVICE, true);
// _hyperion->setComponentState(hyperion::COMP_SMOOTHING, true);
2019-04-19 08:22:23 +02:00
}
return ;
}
2019-07-14 22:43:22 +02:00
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 ) ;
}
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 " ) ;
2018-12-31 15:48:29 +01:00
app - > setWindowIcon ( QIcon ( " :/hyperion-icon-32px.png " ) ) ;
2019-07-14 22:43:22 +02:00
return app ;
2017-08-01 17:29:47 +02:00
}
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
2019-07-14 22:43:22 +02:00
// check if we are running already an instance
// TODO Do not use pgrep on linux, instead iter /proc
// TODO Allow one session per user
// http://www.qtcentre.org/threads/44489-Get-Process-ID-for-a-running-application
QStringList listOfPids ;
if ( getProcessIdsByProcessName ( " hyperiond " , listOfPids ) > 1 )
{
Error ( log , " The Hyperion Daemon is already running, abort start " ) ;
return 0 ;
}
2016-06-17 01:25:40 +02:00
// Initialising QCoreApplication
2019-07-14 22:43:22 +02:00
QScopedPointer < QCoreApplication > app ( createApplication ( argc , argv ) ) ;
2017-08-01 17:29:47 +02:00
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 ) ;
2019-04-19 08:22:23 +02:00
signal ( SIGUSR1 , signal_handler ) ;
signal ( SIGUSR2 , 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
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 & exportEfxOption = parser . add < Option > ( 0x0 , " export-effects " , " export effects to given path " ) ;
2019-07-14 22:43:22 +02:00
parser . process ( * qApp ) ;
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 )
{
2019-01-07 18:13:49 +01:00
Error ( log , " aborting, because options --silent --verbose --debug can't be used together " ) ;
2016-06-23 13:48:49 +02:00
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
{
2019-07-14 22:43:22 +02:00
std : : cout
< < " Hyperion Ambilight Deamon ( " < < getpid ( ) < < " ) " < < std : : endl
< < " \t Version : " < < HYPERION_VERSION < < " ( " < < HYPERION_BUILD_ID < < " ) " < < std : : endl
< < " \t Build Time: " < < __DATE__ < < " " < < __TIME__ < < std : : endl ;
2016-06-17 01:25:40 +02:00
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 ) )
QFile : : remove ( destFileName ) ;
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
2019-07-14 22:43:22 +02:00
// NOTE: No further checks inside Hyperion. FileUtils::writeFile() will resolve permission errors and others that occur during runtime
2017-10-12 11:55:03 +02:00
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
2016-06-20 23:41:07 +02:00
HyperionDaemon * hyperiond = nullptr ;
try
{
2019-07-14 22:43:22 +02:00
hyperiond = new HyperionDaemon ( 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 ;
}