Files
ResInsight/ApplicationExeCode/RiaMain.cpp
T
Magne Sjaastad b20fcd5138 #11802 Suppress harmless Qt6 invalid-primaries QColorSpace warning
Install a Qt message handler at startup that filters out the cosmetic Qt6
warning "QColorSpace attempted constructed from invalid primaries" seen on
some Linux platforms (e.g. RHEL8). The warning originates in Qt6 when it
validates an empty, all-zero QColorSpacePrimaries and has no functional
impact. All other messages are forwarded to the previous handler unchanged.
2026-06-07 16:29:19 +02:00

281 lines
10 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2011-2012 Statoil ASA, Ceetron AS
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiaApplication.h"
#include "RiaArgumentParser.h"
#include "RiaMainTools.h"
#include "RiaOpenTelemetryManager.h"
#include "RiaPreferences.h"
#include "RiaQuantityInfoTools.h"
#include "RiaStdStringTools.h"
#ifdef ENABLE_GRPC
#include "RiaGrpcConsoleApplication.h"
#include "RiaGrpcGuiApplication.h"
#else
#include "RiaConsoleApplication.h"
#include "RiaGuiApplication.h"
#endif
#include "cafUiAppearanceSettings.h"
#include "cvfProgramOptions.h"
#include "cvfqtUtils.h"
#include <QFile>
#include <QtGlobal>
#ifndef WIN32
#include <sys/types.h>
#include <unistd.h>
#endif
#include <signal.h>
void manageSegFailure( int signalCode );
#ifndef WIN32
void manageSegFailureSA( int signalCode, siginfo_t* info, void* ucontext );
#endif
namespace
{
QtMessageHandler g_previousMessageHandler = nullptr;
//--------------------------------------------------------------------------------------------------
/// Filter out a harmless Qt6 warning seen on some Linux platforms (e.g. RHEL8) where Qt validates an
/// empty (all-zero) QColorSpacePrimaries and prints:
/// "QColorSpace attempted constructed from invalid primaries: QPointF(0,0) ..."
/// The message has no functional impact, so suppress it while forwarding everything else unchanged.
/// See https://github.com/OPM/ResInsight/issues/11802
//--------------------------------------------------------------------------------------------------
void riaFilteredMessageHandler( QtMsgType type, const QMessageLogContext& context, const QString& msg )
{
if ( msg.startsWith( QLatin1String( "QColorSpace attempted constructed from invalid primaries" ) ) )
{
return;
}
if ( g_previousMessageHandler )
{
g_previousMessageHandler( type, context, msg );
}
}
} // namespace
RiaApplication* createApplication( int& argc, char* argv[] )
{
for ( int i = 1; i < argc; ++i )
{
if ( !qstrcmp( argv[i], "--console" ) || !qstrcmp( argv[i], "--unittest" ) )
{
#ifdef ENABLE_GRPC
return new RiaGrpcConsoleApplication( argc, argv );
#else
return new RiaConsoleApplication( argc, argv );
#endif
}
}
#ifdef ENABLE_GRPC
return new RiaGrpcGuiApplication( argc, argv );
#else
return new RiaGuiApplication( argc, argv );
#endif
}
int main( int argc, char* argv[] )
{
#ifndef WIN32
// From Qt 5.3 and onwards Qt has a mechanism for checking this automatically
// But it only checks user id not group id, so better to do it ourselves.
if ( getuid() != geteuid() || getgid() != getegid() )
{
std::cerr << "FATAL: The application binary appears to be running setuid or setgid, this is a security hole."
<< std::endl;
return 1;
}
#endif
// Install a message handler to filter out a harmless Qt6 color-space warning (see #11802)
g_previousMessageHandler = qInstallMessageHandler( riaFilteredMessageHandler );
RiaMainTools::deleteStaleSettingsLockFiles();
// The Qt::AA_ShareOpenGLContexts setting is needed when we have multiple viz widgets in flight
// and we have a setup where these widgets belong to different top-level windows, or end up
// belonging to different top-level windows through re-parenting.
// See test application QtTestBenchOpenGLWidget
QApplication::setAttribute( Qt::AA_ShareOpenGLContexts );
// Create feature manager before the application object is created
RiaMainTools::initializeSingletons();
RiaQuantityInfoTools::initializeSummaryKeywords();
// https://www.w3.org/wiki/CSS/Properties/color/keywords
caf::UiAppearanceSettings::instance()->setAutoValueEditorColor( "moccasin" );
std::unique_ptr<RiaApplication> app( createApplication( argc, argv ) );
cvf::ProgramOptions progOpt;
bool result = RiaArgumentParser::parseArguments( &progOpt );
const cvf::String usageText = progOpt.usageText( 110, 30 );
app->initialize();
app->setCommandLineHelpText( cvfqt::Utils::toQString( usageText ) );
// Report application startup to OpenTelemetry
{
std::map<std::string, std::string> attributes;
bool isConsoleMode = ( dynamic_cast<RiaGuiApplication*>( app.get() ) == nullptr );
attributes["app.console_mode"] = RiaStdStringTools::boolToString( isConsoleMode );
attributes["app.version"] = RiaApplication::getVersionStringApp( false );
RiaOpenTelemetryManager::instance().reportEventAsync( "app.started", attributes );
}
if ( !result )
{
std::vector<cvf::String> unknownOptions = progOpt.unknownOptions();
QString unknownOptionsText;
for ( cvf::String option : unknownOptions )
{
unknownOptionsText += QString( "\tUnknown option: %1\n" ).arg( cvfqt::Utils::toQString( option ) );
}
app->showFormattedTextInMessageBoxOrConsole(
"ERROR: Unknown command line options detected ! \n" + unknownOptionsText + "\n\n" +
"The current command line options in ResInsight are:\n" + app->commandLineParameterHelp() );
if ( dynamic_cast<RiaGuiApplication*>( app.get() ) == nullptr )
{
return 1;
}
}
QLocale::setDefault( QLocale( QLocale::English, QLocale::UnitedStates ) );
setlocale( LC_NUMERIC, "C" );
// Set up signal handlers for genuine fault/abort signals only.
// SIGINT and SIGTERM are graceful termination requests (Ctrl-C, kill, or a
// controlling process such as the Python rips client). They must NOT be routed
// through the crash handler: doing so captures the stack trace of wherever the
// main thread happens to be parked - e.g. inside the multi-second gRPC server
// EventEngine teardown during normal shutdown - and reports it as a spurious
// "Crash". Leaving them with their default disposition lets the process
// terminate cleanly.
#ifndef WIN32
// Use SA_SIGINFO on Linux to capture fault address, signal code, and program counter
struct sigaction sa{};
sa.sa_sigaction = manageSegFailureSA;
sa.sa_flags = SA_SIGINFO;
sigemptyset( &sa.sa_mask );
sigaction( SIGILL, &sa, nullptr );
sigaction( SIGFPE, &sa, nullptr );
sigaction( SIGSEGV, &sa, nullptr );
sigaction( SIGABRT, &sa, nullptr );
#else
signal( SIGILL, manageSegFailure );
signal( SIGFPE, manageSegFailure );
signal( SIGSEGV, manageSegFailure );
signal( SIGABRT, manageSegFailure );
#endif
// Handle the command line arguments.
// Todo: Move to a one-shot timer, delaying the execution until we are inside the event loop.
// The complete handling of the resulting ApplicationStatus must be moved along.
// The reason for this is: deleteLater() does not work outside the event loop
// Make execution of command line stuff operate in identical conditions as interactive operation.
RiaApplication::ApplicationStatus status = app->handleArguments( &progOpt );
if ( status == RiaApplication::ApplicationStatus::EXIT_COMPLETED )
{
// Make sure project is closed to avoid assert and crash in destruction of widgets
app->closeProject();
app.reset();
RiaMainTools::releaseSingletonAndFactoryObjects();
return 0;
}
else if ( status == RiaApplication::ApplicationStatus::EXIT_WITH_ERROR )
{
// Make sure project is closed to avoid assert and crash in destruction of widgets
app->closeProject();
return 2;
}
else if ( status == RiaApplication::ApplicationStatus::KEEP_GOING )
{
int exitCode = 0;
try
{
#ifdef ENABLE_GRPC
auto grpcInterface = dynamic_cast<RiaGrpcApplicationInterface*>( app.get() );
if ( grpcInterface && grpcInterface->initializeGrpcServer( progOpt ) )
{
grpcInterface->launchGrpcServer();
if ( cvf::Option o = progOpt.option( "portnumberfile" ) )
{
if ( o.valueCount() == 1 )
{
int portNumber = grpcInterface->portNumber();
QString fileName = QString::fromStdString( o.value( 0 ).toStdString() );
// Write port number to the file given file.
// Temp file is used to avoid incomplete reads.
QString tempFilePath = fileName + ".tmp";
QFile file( tempFilePath );
if ( file.open( QIODevice::WriteOnly | QIODevice::Text ) )
{
QTextStream out( &file );
out << portNumber << "\n";
}
file.close();
QFile::rename( tempFilePath, fileName );
}
}
}
#endif
// Check if max thread count is set either from command line or preferences
app->setThreadCount();
exitCode = QCoreApplication::instance()->exec();
}
catch ( std::exception& exep )
{
std::cout << "A standard c++ exception that terminated ResInsight caught in RiaMain.cpp: " << exep.what()
<< std::endl;
throw;
}
catch ( ... )
{
std::cout << "An unknown exception that terminated ResInsight caught in RiaMain.cpp. " << std::endl;
throw;
}
app.reset();
RiaMainTools::releaseSingletonAndFactoryObjects();
return exitCode;
}
CVF_ASSERT( false && "Unknown ApplicationStatus" );
return -1;
}