#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "revision_utils.hpp" #include "MetaDataRegistry.hpp" #include "L10nLoader.hpp" #include "SettingsGroup.hpp" #include "TraceFile.hpp" #include "MultiSettings.hpp" #include "widgets/mainwindow.h" #include "commons.h" #include "lib/init_random_seed.h" #include "Radio.hpp" #include "models/FrequencyList.hpp" #include "widgets/SplashScreen.hpp" #include "widgets/MessageBox.hpp" // last to avoid nasty MS macro definitions extern "C" { // Fortran procedures we need void four2a_(_Complex float *, int * nfft, int * ndim, int * isign, int * iform, int len); } namespace { #if QT_VERSION < QT_VERSION_CHECK (5, 15, 0) struct RNGSetup { RNGSetup () { // one time seed of pseudo RNGs from current time auto seed = QDateTime::currentMSecsSinceEpoch (); qsrand (seed); // this is good for rand() as well } } seeding; #endif // We can't use the GUI after QApplication::exit() is called so // uncaught exceptions can get lost on Windows systems where there // is no console terminal, so here we override // QApplication::notify() and wrap the base class call with a try // block to catch and display exceptions in a message box. class ExceptionCatchingApplication final : public QApplication { public: explicit ExceptionCatchingApplication (int& argc, char * * argv) : QApplication {argc, argv} { } bool notify (QObject * receiver, QEvent * e) override { try { return QApplication::notify (receiver, e); } catch (std::exception const& e) { MessageBox::critical_message (nullptr, translate ("main", "Fatal error"), e.what ()); throw; } catch (...) { MessageBox::critical_message (nullptr, translate ("main", "Unexpected fatal error")); throw; } } }; } int main(int argc, char *argv[]) { // ### Add timestamps to all debug messages // qSetMessagePattern ("[%{time yyyyMMdd HH:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{message}"); init_random_seed (); // make the Qt type magic happen Radio::register_types (); register_types (); // Multiple instances communicate with jt9 via this QSharedMemory mem_jt9; ExceptionCatchingApplication a(argc, argv); try { // qDebug () << "+++++++++++++++++++++++++++ Resources ++++++++++++++++++++++++++++"; // { // QDirIterator resources_iter {":/", QDirIterator::Subdirectories}; // while (resources_iter.hasNext ()) // { // qDebug () << resources_iter.next (); // } // } // qDebug () << "--------------------------- Resources ----------------------------"; QLocale locale; // get the current system locale setlocale (LC_NUMERIC, "C"); // ensure number forms are in // consistent format, do this after // instantiating QApplication so // that GUI has correct l18n // Override programs executable basename as application name. a.setApplicationName ("WSJT-X"); a.setApplicationVersion (version ()); QCommandLineParser parser; parser.setApplicationDescription ("\n" PROJECT_SUMMARY_DESCRIPTION); auto help_option = parser.addHelpOption (); auto version_option = parser.addVersionOption (); // support for multiple instances running from a single installation QCommandLineOption rig_option (QStringList {} << "r" << "rig-name" , "Where is for multi-instance support." , "rig-name"); parser.addOption (rig_option); // support for start up configuration QCommandLineOption cfg_option (QStringList {} << "c" << "config" , "Where is an existing one." , "configuration"); parser.addOption (cfg_option); // support for UI language override (useful on Windows) QCommandLineOption lang_option (QStringList {} << "l" << "language" , "Where is [-]." , "language"); parser.addOption (lang_option); QCommandLineOption test_option (QStringList {} << "test-mode" , "Writable files in test location. Use with caution, for testing only."); parser.addOption (test_option); if (!parser.parse (a.arguments ())) { MessageBox::critical_message (nullptr, "Command line error", parser.errorText ()); return -1; } else { if (parser.isSet (help_option)) { MessageBox::information_message (nullptr, "Command line help", parser.helpText ()); return 0; } else if (parser.isSet (version_option)) { MessageBox::information_message (nullptr, "Application version", a.applicationVersion ()); return 0; } } // load UI translations L10nLoader l10n {&a, locale, parser.value (lang_option)}; QStandardPaths::setTestModeEnabled (parser.isSet (test_option)); // support for multiple instances running from a single installation bool multiple {false}; if (parser.isSet (rig_option) || parser.isSet (test_option)) { auto temp_name = parser.value (rig_option); if (!temp_name.isEmpty ()) { if (temp_name.contains (QRegularExpression {R"([\\/,])"})) { std::cerr << "Invalid rig name - \\ & / not allowed" << std::endl; parser.showHelp (-1); } a.setApplicationName (a.applicationName () + " - " + temp_name); } if (parser.isSet (test_option)) { a.setApplicationName (a.applicationName () + " - test"); } multiple = true; } // now we have the application name we can open the settings MultiSettings multi_settings {parser.value (cfg_option)}; // find the temporary files path QDir temp_dir {QStandardPaths::writableLocation (QStandardPaths::TempLocation)}; Q_ASSERT (temp_dir.exists ()); // sanity check // disallow multiple instances with same instance key QLockFile instance_lock {temp_dir.absoluteFilePath (a.applicationName () + ".lock")}; instance_lock.setStaleLockTime (0); bool lock_ok {false}; while (!(lock_ok = instance_lock.tryLock ())) { if (QLockFile::LockFailedError == instance_lock.error ()) { auto button = MessageBox::query_message (nullptr , a.translate ("main", "Another instance may be running") , a.translate ("main", "try to remove stale lock file?") , QString {} , MessageBox::Yes | MessageBox::Retry | MessageBox::No , MessageBox::Yes); switch (button) { case MessageBox::Yes: instance_lock.removeStaleLockFile (); break; case MessageBox::Retry: break; default: throw std::runtime_error {"Multiple instances must have unique rig names"}; } } } #if WSJT_QDEBUG_TO_FILE // Open a trace file TraceFile trace_file {temp_dir.absoluteFilePath (a.applicationName () + "_trace.log")}; qSetMessagePattern ("[%{time yyyyMMdd HH:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}"); qDebug () << program_title (revision ()) + " - Program startup"; #endif // Create a unique writeable temporary directory in a suitable location bool temp_ok {false}; QString unique_directory {QApplication::applicationName ()}; do { if (!temp_dir.mkpath (unique_directory) || !temp_dir.cd (unique_directory)) { MessageBox::critical_message (nullptr, a.translate ("main", "Failed to create a temporary directory"), a.translate ("main", "Path: \"%1\"").arg (temp_dir.absolutePath ())); throw std::runtime_error {"Failed to create a temporary directory"}; } if (!temp_dir.isReadable () || !(temp_ok = QTemporaryFile {temp_dir.absoluteFilePath ("test")}.open ())) { auto button = MessageBox::critical_message (nullptr, a.translate ("main", "Failed to create a usable temporary directory"), a.translate ("main", "Another application may be locking the directory"), a.translate ("main", "Path: \"%1\"").arg (temp_dir.absolutePath ()), MessageBox::Retry | MessageBox::Cancel); if (MessageBox::Cancel == button) { throw std::runtime_error {"Failed to create a usable temporary directory"}; } temp_dir.cdUp (); // revert to parent as this one is no good } } while (!temp_ok); SplashScreen splash; { // change this key if you want to force a new splash screen // for a new version, the user will be able to re-disable it // if they wish QString splash_flag_name {"Splash_v1.7"}; if (multi_settings.common_value (splash_flag_name, true).toBool ()) { QObject::connect (&splash, &SplashScreen::disabled, [&, splash_flag_name] { multi_settings.set_common_value (splash_flag_name, false); splash.close (); }); splash.show (); a.processEvents (); } } // create writeable data directory if not already there auto writeable_data_dir = QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}; if (!writeable_data_dir.mkpath (".")) { MessageBox::critical_message (nullptr, a.translate ("main", "Failed to create data directory"), a.translate ("main", "path: \"%1\"").arg (writeable_data_dir.absolutePath ())); throw std::runtime_error {"Failed to create data directory"}; } // set up SQLite database if (!QSqlDatabase::drivers ().contains ("QSQLITE")) { throw std::runtime_error {"Failed to find SQLite Qt driver"}; } auto db = QSqlDatabase::addDatabase ("QSQLITE"); db.setDatabaseName (writeable_data_dir.absoluteFilePath ("db.sqlite")); if (!db.open ()) { throw std::runtime_error {("Database Error: " + db.lastError ().text ()).toStdString ()}; } // better performance traded for a risk of d/b corruption // on system crash or application crash // db.exec ("PRAGMA synchronous=OFF"); // system crash risk // db.exec ("PRAGMA journal_mode=MEMORY"); // application crash risk db.exec ("PRAGMA locking_mode=EXCLUSIVE"); int result; auto const& original_style_sheet = a.styleSheet (); do { #if WSJT_QDEBUG_TO_FILE // announce to trace file and dump settings qDebug () << "++++++++++++++++++++++++++++ Settings ++++++++++++++++++++++++++++"; for (auto const& key: multi_settings.settings ()->allKeys ()) { auto const& value = multi_settings.settings ()->value (key); if (value.canConvert ()) { auto const sequence = value.value (); qDebug ().nospace () << key << ": "; for (auto const& item: sequence) { qDebug ().nospace () << '\t' << item; } } else { qDebug ().nospace () << key << ": " << value; } } qDebug () << "---------------------------- Settings ----------------------------"; #endif // Create and initialize shared memory segment // Multiple instances: use rig_name as shared memory key mem_jt9.setKey(a.applicationName ()); // try and shut down any orphaned jt9 process for (int i = 3; i; --i) // three tries to close old jt9 { if (mem_jt9.attach ()) // shared memory presence implies // orphaned jt9 sub-process { dec_data_t * dd = reinterpret_cast (mem_jt9.data()); mem_jt9.lock (); dd->ipc[1] = 999; // tell jt9 to shut down mem_jt9.unlock (); mem_jt9.detach (); // start again } else { break; // good to go } QThread::sleep (1); // wait for jt9 to end } if (!mem_jt9.attach ()) { if (!mem_jt9.create (sizeof (struct dec_data))) { splash.hide (); MessageBox::critical_message (nullptr, a.translate ("main", "Shared memory error"), a.translate ("main", "Unable to create shared memory segment")); throw std::runtime_error {"Shared memory error"}; } } else { splash.hide (); MessageBox::critical_message (nullptr, a.translate ("main", "Sub-process error"), a.translate ("main", "Failed to close orphaned jt9 process")); throw std::runtime_error {"Sub-process error"}; } mem_jt9.lock (); memset(mem_jt9.data(),0,sizeof(struct dec_data)); //Zero all decoding params in shared memory mem_jt9.unlock (); unsigned downSampleFactor; { SettingsGroup {multi_settings.settings (), "Tune"}; // deal with Windows Vista and earlier input audio rate // converter problems downSampleFactor = multi_settings.settings ()->value ("Audio/DisableInputResampling", #if defined (Q_OS_WIN) // default to true for // Windows Vista and older QSysInfo::WV_VISTA >= QSysInfo::WindowsVersion ? true : false #else false #endif ).toBool () ? 1u : 4u; } // run the application UI MainWindow w(temp_dir, multiple, &multi_settings, &mem_jt9, downSampleFactor, &splash); w.show(); splash.raise (); QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit())); result = a.exec(); // ensure config switches start with the right style sheet a.setStyleSheet (original_style_sheet); } while (!result && !multi_settings.exit ()); // clean up lazily initialized resources { int nfft {-1}; int ndim {1}; int isign {1}; int iform {1}; // free FFT plan resources four2a_ (nullptr, &nfft, &ndim, &isign, &iform, 0); } fftwf_forget_wisdom (); fftwf_cleanup (); temp_dir.removeRecursively (); // clean up temp files return result; } catch (std::exception const& e) { MessageBox::critical_message (nullptr, QApplication::translate ("main", "Fatal error"), e.what ()); std::cerr << "Error: " << e.what () << '\n'; } catch (...) { MessageBox::critical_message (nullptr, QApplication::translate ("main", "Unexpected fatal error")); std::cerr << "Unexpected fatal error\n"; throw; // hoping the runtime might tell us more about the exception } return -1; }