mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-31 04:50:34 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			453 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			453 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <iostream>
 | |
| #include <exception>
 | |
| #include <stdexcept>
 | |
| #include <string>
 | |
| 
 | |
| #include <locale.h>
 | |
| #include <fftw3.h>
 | |
| 
 | |
| #include <QSharedMemory>
 | |
| #include <QTemporaryFile>
 | |
| #include <QDateTime>
 | |
| #include <QApplication>
 | |
| #include <QLocale>
 | |
| #include <QTranslator>
 | |
| #include <QRegularExpression>
 | |
| #include <QObject>
 | |
| #include <QSettings>
 | |
| #include <QSysInfo>
 | |
| #include <QDir>
 | |
| #include <QDirIterator>
 | |
| #include <QStandardPaths>
 | |
| #include <QStringList>
 | |
| #include <QLockFile>
 | |
| #include <QSplashScreen>
 | |
| #include <QCommandLineParser>
 | |
| #include <QCommandLineOption>
 | |
| #include <QSqlDatabase>
 | |
| #include <QSqlQuery>
 | |
| #include <QSqlError>
 | |
| 
 | |
| #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, "Fatal error", e.what ());
 | |
|           throw;
 | |
|         }
 | |
|       catch (...)
 | |
|         {
 | |
|           MessageBox::critical_message (nullptr, "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 <rig-name> is for multi-instance support."
 | |
|                                      , "rig-name");
 | |
|       parser.addOption (rig_option);
 | |
| 
 | |
|       // support for start up configuration
 | |
|       QCommandLineOption cfg_option (QStringList {} << "c" << "config"
 | |
|                                      , "Where <configuration> is an existing one."
 | |
|                                      , "configuration");
 | |
|       parser.addOption (cfg_option);
 | |
| 
 | |
|       // support for UI language override (useful on Windows)
 | |
|       QCommandLineOption lang_option (QStringList {} << "l" << "language"
 | |
|                                      , "Where <language> is <lang-code>[-<country-code>]."
 | |
|                                      , "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<QVariantList> ())
 | |
|                 {
 | |
|                   auto const sequence = value.value<QSequentialIterable> ();
 | |
|                   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<dec_data_t *> (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 (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"};
 | |
|               }
 | |
|               qDebug () << "shmem size:" << mem_jt9.size ();
 | |
|             }
 | |
|           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, "Fatal error", e.what ());
 | |
|       std::cerr << "Error: " << e.what () << '\n';
 | |
|     }
 | |
|   catch (...)
 | |
|     {
 | |
|       MessageBox::critical_message (nullptr, "Unexpected fatal error");
 | |
|       std::cerr << "Unexpected fatal error\n";
 | |
|       throw;			// hoping the runtime might tell us more about the exception
 | |
|     }
 | |
|   return -1;
 | |
| }
 |