2016-06-17 01:25:40 +02:00
# include <cassert>
# include <csignal>
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
2020-05-12 19:51:19 +02:00
# if !defined(__APPLE__) && !defined(_WIN32)
2016-08-07 18:39:45 +02:00
/* prctl is Linux only */
# include <sys/prctl.h>
# endif
2020-05-12 19:51:19 +02:00
// getpid()
# ifdef _WIN32
2020-07-12 09:18:40 +02:00
# include "console.h"
2020-05-12 19:51:19 +02:00
# include <process.h>
# else
# include <unistd.h>
# endif
2016-08-07 18:39:45 +02:00
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>
2021-11-17 21:30:43 +01:00
# include <QNetworkInterface>
# include <QHostInfo>
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>
2020-07-12 18:27:24 +02:00
# include <utils/DefaultSignalHandler.h>
2019-09-17 21:33:46 +02:00
# include <../../include/db/AuthTable.h>
2016-06-17 01:25:40 +02:00
2020-05-12 19:51:19 +02:00
# include "detectProcess.h"
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
2021-11-17 21:30:43 +01:00
# define PERM0664 (QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther | QFileDevice::WriteOwner | QFileDevice::WriteGroup)
2016-12-03 21:11:52 +01:00
2020-05-12 19:51:19 +02:00
# ifndef _WIN32
2020-08-08 13:09:15 +02:00
void signal_handler ( int signum )
2016-06-17 01:25:40 +02:00
{
2019-07-14 22:43:22 +02:00
// Hyperion Managment instance
2020-05-12 19:51:19 +02:00
HyperionIManager * _hyperion = HyperionIManager : : getInstance ( ) ;
2019-04-19 08:22:23 +02:00
2020-05-12 19:51:19 +02:00
if ( signum = = SIGCHLD )
2017-01-29 21:20:12 +01:00
{
// only quit when a registered child process is gone
// currently this feature is not active ...
}
2019-04-19 08:22:23 +02:00
else if ( signum = = SIGUSR1 )
{
if ( _hyperion ! = nullptr )
{
2020-03-26 19:37:39 +01:00
_hyperion - > toggleStateAllInstances ( false ) ;
2019-04-19 08:22:23 +02:00
}
}
else if ( signum = = SIGUSR2 )
{
if ( _hyperion ! = nullptr )
{
2020-03-26 19:37:39 +01:00
_hyperion - > toggleStateAllInstances ( true ) ;
2019-04-19 08:22:23 +02:00
}
}
2016-06-17 01:25:40 +02:00
}
2020-05-12 19:51:19 +02:00
# endif
2016-06-17 01:25:40 +02:00
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
2020-05-12 19:51:19 +02:00
# if defined(__APPLE__) || defined(_WIN32)
2017-08-01 17:29:47 +02:00
isGuiApp = true & & ! forceNoGui ;
# else
if ( ! forceNoGui )
{
// if x11, then test if xserver is available
2020-08-03 12:31:39 +02:00
# if defined(ENABLE_X11)
2021-11-17 21:30:43 +01:00
Display * dpy = XOpenDisplay ( nullptr ) ;
if ( dpy ! = nullptr )
2017-08-01 17:29:47 +02:00
{
XCloseDisplay ( dpy ) ;
isGuiApp = true ;
}
2020-08-03 12:31:39 +02:00
# elif defined(ENABLE_XCB)
int screen_num ;
xcb_connection_t * connection = xcb_connect ( nullptr , & screen_num ) ;
if ( ! xcb_connection_has_error ( connection ) )
{
isGuiApp = true ;
}
xcb_disconnect ( connection ) ;
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 ) ;
2020-02-16 16:24:33 +01:00
// add optional library path
app - > addLibraryPath ( QApplication : : applicationDirPath ( ) + " /../lib " ) ;
2017-08-01 17:29:47 +02:00
app - > setApplicationDisplayName ( " Hyperion " ) ;
2021-10-16 14:07:36 +02:00
# ifndef __APPLE__
2018-12-31 15:48:29 +01:00
app - > setWindowIcon ( QIcon ( " :/hyperion-icon-32px.png " ) ) ;
2021-10-16 14:07:36 +02:00
# endif
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 ) ;
2020-02-16 16:24:33 +01:00
// add optional library path
app - > addLibraryPath ( QApplication : : applicationDirPath ( ) + " /../lib " ) ;
2017-08-01 17:29:47 +02:00
return app ;
}
2016-06-17 01:25:40 +02:00
int main ( int argc , char * * argv )
{
2020-05-12 19:51:19 +02:00
# ifndef _WIN32
2017-03-21 17:55:46 +01:00
setenv ( " AVAHI_COMPAT_NOWARN " , " 1 " , 1 ) ;
2020-05-12 19:51:19 +02:00
# endif
2016-06-21 21:41:26 +02:00
// initialize main logger and set global log level
2020-05-12 19:51:19 +02:00
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 Allow one session per user
2020-05-12 19:51:19 +02:00
# ifdef _WIN32
const char * processName = " hyperiond.exe " ;
# else
const char * processName = " hyperiond " ;
# endif
2019-07-14 22:43:22 +02:00
2016-06-17 01:25:40 +02:00
// Initialising QCoreApplication
2019-07-14 22:43:22 +02:00
QScopedPointer < QCoreApplication > app ( createApplication ( argc , argv ) ) ;
2021-11-17 21:30:43 +01:00
bool isGuiApp = ( qobject_cast < QApplication * > ( app . data ( ) ) ! = nullptr & & QSystemTrayIcon : : isSystemTrayAvailable ( ) ) ;
2016-06-17 01:25:40 +02:00
2020-07-12 18:27:24 +02:00
DefaultSignalHandler : : install ( ) ;
2020-05-12 19:51:19 +02:00
# ifndef _WIN32
2016-06-17 01:25:40 +02:00
signal ( SIGCHLD , signal_handler ) ;
2019-04-19 08:22:23 +02:00
signal ( SIGUSR1 , signal_handler ) ;
signal ( SIGUSR2 , signal_handler ) ;
2020-05-12 19:51:19 +02:00
# endif
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
2019-09-17 21:33:46 +02:00
BooleanOption & versionOption = parser . add < BooleanOption > ( 0x0 , " version " , " Show version information " ) ;
Option & userDataOption = parser . add < Option > ( ' u ' , " userdata " , " Overwrite user data path, defaults to home directory of current user (%1) " , QDir : : homePath ( ) + " /.hyperion " ) ;
BooleanOption & resetPassword = parser . add < BooleanOption > ( 0x0 , " resetPassword " , " Lost your password? Reset it with this option back to 'hyperion' " ) ;
2020-06-17 20:59:26 +02:00
BooleanOption & deleteDB = parser . add < BooleanOption > ( 0x0 , " deleteDatabase " , " Start all over? This Option will delete the database " ) ;
2020-07-22 18:15:39 +02:00
BooleanOption & silentOption = parser . add < BooleanOption > ( ' s ' , " silent " , " Do not print any outputs " ) ;
2019-09-17 21:33:46 +02:00
BooleanOption & verboseOption = parser . add < BooleanOption > ( ' v ' , " verbose " , " Increase verbosity " ) ;
BooleanOption & debugOption = parser . add < BooleanOption > ( ' d ' , " debug " , " Show debug messages " ) ;
2020-07-12 09:18:40 +02:00
# ifdef WIN32
BooleanOption & consoleOption = parser . add < BooleanOption > ( ' c ' , " console " , " Open a console window to view log output " ) ;
# endif
2020-07-22 18:15:39 +02:00
parser . add < BooleanOption > ( 0x0 , " desktop " , " Show systray on desktop " ) ;
parser . add < BooleanOption > ( 0x0 , " service " , " Force hyperion to start as console service " ) ;
Option & exportEfxOption = parser . add < Option > ( 0x0 , " export-effects " , " Export effects to given path " ) ;
/* Internal options, invisible to help */
BooleanOption & waitOption = parser . addHidden < BooleanOption > ( 0x0 , " wait-hyperion " , " Do not exit if other Hyperion instances are running, wait them to finish " ) ;
2016-09-17 00:40:29 +02:00
2019-07-14 22:43:22 +02:00
parser . process ( * qApp ) ;
2016-06-17 01:25:40 +02:00
2021-11-17 21:30:43 +01:00
# ifdef WIN32
if ( parser . isSet ( consoleOption ) )
{
CreateConsole ( ) ;
}
# endif
2020-10-20 20:18:51 +02:00
if ( parser . isSet ( versionOption ) )
{
std : : cout
< < " Hyperion Ambilight Deamon " < < std : : endl
< < " \t Version : " < < HYPERION_VERSION < < " ( " < < HYPERION_BUILD_ID < < " ) " < < std : : endl
< < " \t Build Time: " < < __DATE__ < < " " < < __TIME__ < < std : : endl ;
return 0 ;
}
2020-07-22 18:15:39 +02:00
if ( ! parser . isSet ( waitOption ) )
{
if ( getProcessIdsByProcessName ( processName ) . size ( ) > 1 )
{
Error ( log , " The Hyperion Daemon is already running, abort start " ) ;
2021-11-17 21:30:43 +01:00
// use the first non-localhost IPv4 address, IPv6 are not supported by Yeelight currently
for ( const auto & address : QNetworkInterface : : allAddresses ( ) )
{
if ( ! address . isLoopback ( ) & & ( address . protocol ( ) = = QAbstractSocket : : IPv4Protocol ) )
{
std : : cout < < " Access the Hyperion User-Interface for configuration and control via: " < < std : : endl ;
2021-11-29 18:51:03 +01:00
std : : cout < < " http:// " < < address . toString ( ) . toStdString ( ) < < " :8090 " < < std : : endl ;
2021-11-17 21:30:43 +01:00
QHostInfo hostInfo = QHostInfo : : fromName ( address . toString ( ) ) ;
if ( hostInfo . error ( ) = = QHostInfo : : NoError )
{
QString hostname = hostInfo . hostName ( ) ;
2021-11-29 18:51:03 +01:00
std : : cout < < " http:// " < < hostname . toStdString ( ) < < " :8090 " < < std : : endl ;
2021-11-17 21:30:43 +01:00
}
break ;
}
}
2020-07-22 18:15:39 +02:00
return 0 ;
}
}
else
{
while ( getProcessIdsByProcessName ( processName ) . size ( ) > 1 )
{
QThread : : msleep ( 100 ) ;
}
}
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-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 ( ) )
{
2020-11-14 17:40:15 +01:00
std : : cout < < " Extract to folder: " < < destDir . absolutePath ( ) . toStdString ( ) < < 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 ;
2021-11-17 21:30:43 +01:00
for ( const QString & filename : qAsConst ( filenames ) )
2016-09-17 00:40:29 +02:00
{
2016-12-03 21:11:52 +01:00
destFileName = destDir . dirName ( ) + " / " + filename ;
if ( QFile : : exists ( destFileName ) )
2021-11-17 21:30:43 +01:00
{
2016-12-03 21:11:52 +01:00
QFile : : remove ( destFileName ) ;
2021-11-17 21:30:43 +01: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 ) ;
2020-11-01 19:47:30 +01:00
std : : cout < < " OK " < < std : : endl ;
2016-09-17 00:40:29 +02:00
}
else
{
2020-11-14 17:40:15 +01:00
std : : cout < < " Error, aborting " < < std : : endl ;
return 1 ;
2016-09-17 00:40:29 +02:00
}
}
return 0 ;
}
2020-11-01 19:47:30 +01:00
Error ( log , " Can not export to %s " , exportEfxOption . getCString ( parser ) ) ;
2016-09-17 00:40:29 +02:00
return 1 ;
}
2016-10-13 21:59:10 +02:00
2016-07-10 12:18:40 +02:00
int rc = 1 ;
2020-11-01 19:47:30 +01:00
bool readonlyMode = false ;
2019-08-15 23:49:32 +02:00
2020-11-14 17:40:15 +01:00
QString userDataPath ( userDataOption . value ( parser ) ) ;
QDir userDataDirectory ( userDataPath ) ;
QFileInfo dbFile ( userDataDirectory . absolutePath ( ) + " /db/hyperion.db " ) ;
2016-07-10 12:18:40 +02:00
try
{
2020-11-14 17:40:15 +01:00
if ( dbFile . exists ( ) )
2020-11-01 19:47:30 +01:00
{
2020-11-14 17:40:15 +01:00
if ( ! dbFile . isReadable ( ) )
2020-11-01 19:47:30 +01:00
{
2020-11-14 17:40:15 +01:00
throw std : : runtime_error ( " Configuration database ' " + dbFile . absoluteFilePath ( ) . toStdString ( ) + " ' is not readable. Please setup permissions correctly! " ) ;
2020-11-01 19:47:30 +01:00
}
2021-11-17 21:30:43 +01:00
if ( ! dbFile . isWritable ( ) )
2020-11-01 19:47:30 +01:00
{
2021-11-17 21:30:43 +01:00
readonlyMode = true ;
2020-11-14 17:40:15 +01:00
}
}
else
{
if ( ! userDataDirectory . mkpath ( dbFile . absolutePath ( ) ) )
{
if ( ! userDataDirectory . isReadable ( ) | | ! dbFile . isWritable ( ) )
2020-11-01 19:47:30 +01:00
{
2020-11-14 17:40:15 +01:00
throw std : : runtime_error ( " The user data path ' " + userDataDirectory . absolutePath ( ) . toStdString ( ) + " ' can't be created or isn't read/writeable. Please setup permissions correctly! " ) ;
2020-11-01 19:47:30 +01:00
}
}
}
2019-08-17 09:44:57 +02:00
2019-09-17 21:33:46 +02:00
// reset Password without spawning daemon
if ( parser . isSet ( resetPassword ) )
{
2020-11-01 19:47:30 +01:00
if ( readonlyMode )
{
2020-11-14 17:40:15 +01:00
Error ( log , " Password reset is not possible. The user data path '%s' is not writeable. " , QSTRING_CSTR ( userDataDirectory . absolutePath ( ) ) ) ;
2020-11-01 19:47:30 +01:00
throw std : : runtime_error ( " Password reset failed " ) ;
}
2021-11-17 21:30:43 +01:00
AuthTable * table = new AuthTable ( userDataDirectory . absolutePath ( ) ) ;
if ( table - > resetHyperionUser ( ) ) {
Info ( log , " Password reset successful " ) ;
delete table ;
exit ( 0 ) ;
} else {
Error ( log , " Failed to reset password! " ) ;
delete table ;
exit ( 1 ) ;
2019-09-17 21:33:46 +02:00
}
}
2020-06-17 20:59:26 +02:00
// delete database before start
if ( parser . isSet ( deleteDB ) )
{
2020-11-01 19:47:30 +01:00
if ( readonlyMode )
{
2020-11-14 17:40:15 +01:00
Error ( log , " Deleting the configuration database is not possible. The user data path '%s' is not writeable. " , QSTRING_CSTR ( dbFile . absolutePath ( ) ) ) ;
2020-11-01 19:47:30 +01:00
throw std : : runtime_error ( " Deleting the configuration database failed " ) ;
}
2021-11-17 21:30:43 +01:00
if ( QFile : : exists ( dbFile . absoluteFilePath ( ) ) )
2020-06-17 20:59:26 +02:00
{
2021-11-17 21:30:43 +01:00
if ( ! QFile : : remove ( dbFile . absoluteFilePath ( ) ) )
2020-06-17 20:59:26 +02:00
{
2021-11-17 21:30:43 +01:00
Info ( log , " Failed to delete Database! " ) ;
exit ( 1 ) ;
2020-11-01 19:47:30 +01:00
}
else
{
2021-11-17 21:30:43 +01:00
Info ( log , " Configuration database deleted successfully. " ) ;
2020-06-17 20:59:26 +02:00
}
}
2021-11-17 21:30:43 +01:00
else
{
Warning ( log , " Configuration database [%s] does not exist! " , QSTRING_CSTR ( dbFile . absoluteFilePath ( ) ) ) ;
}
2020-06-17 20:59:26 +02:00
}
2020-10-20 20:18:51 +02:00
Info ( log , " Starting Hyperion - %s, %s, built: %s:%s " , HYPERION_VERSION , HYPERION_BUILD_ID , __DATE__ , __TIME__ ) ;
2020-11-14 16:34:31 +01:00
Debug ( log , " QtVersion [%s] " , QT_VERSION_STR ) ;
2020-10-20 20:18:51 +02:00
2020-11-01 19:47:30 +01:00
if ( ! readonlyMode )
{
2020-11-14 17:40:15 +01:00
Info ( log , " Set user data path to '%s' " , QSTRING_CSTR ( userDataDirectory . absolutePath ( ) ) ) ;
2020-11-01 19:47:30 +01:00
}
else
{
2020-11-14 17:40:15 +01:00
Warning ( log , " The user data path '%s' is not writeable. Hyperion starts in read-only mode. Configuration updates will not be persisted! " , QSTRING_CSTR ( userDataDirectory . absolutePath ( ) ) ) ;
2020-11-01 19:47:30 +01:00
}
2020-10-20 20:18:51 +02:00
2020-11-14 17:40:15 +01:00
HyperionDaemon * hyperiond = nullptr ;
2019-08-15 23:49:32 +02:00
try
{
2020-11-14 17:40:15 +01:00
hyperiond = new HyperionDaemon ( userDataDirectory . absolutePath ( ) , qApp , bool ( logLevelCheck ) , readonlyMode ) ;
2019-08-15 23:49:32 +02:00
}
catch ( std : : exception & e )
{
Error ( log , " Hyperion Daemon aborted: %s " , e . what ( ) ) ;
throw ;
}
2016-07-10 12:18:40 +02:00
// 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 ) ;
2019-08-15 23:49:32 +02:00
delete hyperiond ;
2016-07-10 12:18:40 +02:00
}
catch ( std : : exception & e )
{
2019-08-15 23:49:32 +02:00
Error ( log , " Hyperion aborted: %s " , e . what ( ) ) ;
2016-07-10 12:18:40 +02:00
}
2016-06-17 01:25:40 +02:00
2016-06-21 21:41:26 +02:00
// delete components
Logger : : deleteInstance ( ) ;
2020-11-14 17:40:15 +01:00
# ifdef _WIN32
if ( parser . isSet ( consoleOption ) )
{
system ( " pause " ) ;
}
# endif
2016-06-17 01:25:40 +02:00
return rc ;
}