1 #include <iostream>
2 #include <exception>
3 #include <stdexcept>
4 #include <string>
5 #include <iterator>
6 #include <algorithm>
7 #include <ios>
8 #include <locale>
9 #include <fftw3.h>
10
11 #include <QSharedMemory>
12 #include <QProcessEnvironment>
13 #include <QTemporaryFile>
14 #include <QDateTime>
15 #include <QLocale>
16 #include <QTranslator>
17 #include <QRegularExpression>
18 #include <QObject>
19 #include <QSettings>
20 #include <QSysInfo>
21 #include <QDir>
22 #include <QDirIterator>
23 #include <QStandardPaths>
24 #include <QStringList>
25 #include <QLockFile>
26 #include <QSplashScreen>
27 #include <QCommandLineParser>
28 #include <QCommandLineOption>
29 #include <QSqlDatabase>
30 #include <QSqlQuery>
31 #include <QSqlError>
32 #include <QVariant>
33 #include <QByteArray>
34 #include <QBitArray>
35 #include <QMetaType>
36
37 #include "ExceptionCatchingApplication.hpp"
38 #include "Logger.hpp"
39 #include "revision_utils.hpp"
40 #include "MetaDataRegistry.hpp"
41 #include "qt_helpers.hpp"
42 #include "L10nLoader.hpp"
43 #include "SettingsGroup.hpp"
44 //#include "TraceFile.hpp"
45 #include "WSJTXLogging.hpp"
46 #include "MultiSettings.hpp"
47 #include "widgets/mainwindow.h"
48 #include "commons.h"
49 #include "lib/init_random_seed.h"
50 #include "Radio.hpp"
51 #include "models/FrequencyList.hpp"
52 #include "widgets/SplashScreen.hpp"
53 #include "widgets/MessageBox.hpp" // last to avoid nasty MS macro definitions
54
55 extern "C" {
56 // Fortran procedures we need
57 void four2a_(_Complex float *, int * nfft, int * ndim, int * isign, int * iform, int len);
58 }
59
60 namespace
61 {
62 #if QT_VERSION < QT_VERSION_CHECK (5, 15, 0)
63 struct RNGSetup
64 {
RNGSetup__anonb8f43d6e0111::RNGSetup65 RNGSetup ()
66 {
67 // one time seed of pseudo RNGs from current time
68 auto seed = QDateTime::currentMSecsSinceEpoch ();
69 qsrand (seed); // this is good for rand() as well
70 }
71 } seeding;
72 #endif
73
safe_stream_QVariant(boost::log::record_ostream & os,QVariant const & v)74 void safe_stream_QVariant (boost::log::record_ostream& os, QVariant const& v)
75 {
76 switch (static_cast<QMetaType::Type> (v.type ()))
77 {
78 case QMetaType::QByteArray:
79 os << "0x"
80 #if QT_VERSION >= QT_VERSION_CHECK (5, 9, 0)
81 << v.toByteArray ().toHex (':').toStdString ()
82 #else
83 << v.toByteArray ().toHex ().toStdString ()
84 #endif
85 ;
86 break;
87
88 case QMetaType::QBitArray:
89 {
90 auto const& bits = v.toBitArray ();
91 os << "0b";
92 for (int i = 0; i < bits.size (); ++ i)
93 {
94 os << (bits[i] ? '1' : '0');
95 }
96 }
97 break;
98
99 default:
100 os << v.toString ();
101 }
102 }
103 }
104
main(int argc,char * argv[])105 int main(int argc, char *argv[])
106 {
107 init_random_seed ();
108
109 // make the Qt type magic happen
110 Radio::register_types ();
111 register_types ();
112
113 // Multiple instances communicate with jt9 via this
114 QSharedMemory mem_jt9;
115
116 auto const env = QProcessEnvironment::systemEnvironment ();
117
118 ExceptionCatchingApplication a(argc, argv);
119 try
120 {
121 // LOG_INfO ("+++++++++++++++++++++++++++ Resources ++++++++++++++++++++++++++++");
122 // {
123 // QDirIterator resources_iter {":/", QDirIterator::Subdirectories};
124 // while (resources_iter.hasNext ())
125 // {
126 // LOG_INFO (resources_iter.next ());
127 // }
128 // }
129 // LOG_INFO ("--------------------------- Resources ----------------------------");
130
131 QLocale locale; // get the current system locale
132
133 // reset the C+ & C global locales to the classic C locale
134 std::locale::global (std::locale::classic ());
135
136 // Override programs executable basename as application name.
137 a.setApplicationName ("WSJT-X");
138 a.setApplicationVersion (version ());
139
140 QCommandLineParser parser;
141 parser.setApplicationDescription ("\n" PROJECT_DESCRIPTION);
142 auto help_option = parser.addHelpOption ();
143 auto version_option = parser.addVersionOption ();
144
145 // support for multiple instances running from a single installation
146 QCommandLineOption rig_option (QStringList {} << "r" << "rig-name"
147 , "Where <rig-name> is for multi-instance support."
148 , "rig-name");
149 parser.addOption (rig_option);
150
151 // support for start up configuration
152 QCommandLineOption cfg_option (QStringList {} << "c" << "config"
153 , "Where <configuration> is an existing one."
154 , "configuration");
155 parser.addOption (cfg_option);
156
157 // support for UI language override (useful on Windows)
158 QCommandLineOption lang_option (QStringList {} << "l" << "language"
159 , "Where <language> is <lang-code>[-<country-code>]."
160 , "language");
161 parser.addOption (lang_option);
162
163 QCommandLineOption test_option (QStringList {} << "test-mode"
164 , "Writable files in test location. Use with caution, for testing only.");
165 parser.addOption (test_option);
166
167 if (!parser.parse (a.arguments ()))
168 {
169 MessageBox::critical_message (nullptr, "Command line error", parser.errorText ());
170 return -1;
171 }
172 else
173 {
174 if (parser.isSet (help_option))
175 {
176 MessageBox::information_message (nullptr, "Command line help", parser.helpText ());
177 return 0;
178 }
179 else if (parser.isSet (version_option))
180 {
181 MessageBox::information_message (nullptr, "Application version", a.applicationVersion ());
182 return 0;
183 }
184 }
185
186 QStandardPaths::setTestModeEnabled (parser.isSet (test_option));
187
188 // support for multiple instances running from a single installation
189 bool multiple {false};
190 if (parser.isSet (rig_option) || parser.isSet (test_option))
191 {
192 auto temp_name = parser.value (rig_option);
193 if (!temp_name.isEmpty ())
194 {
195 if (temp_name.contains (QRegularExpression {R"([\\/,])"}))
196 {
197 std::cerr << "Invalid rig name - \\ & / not allowed" << std::endl;
198 parser.showHelp (-1);
199 }
200
201 a.setApplicationName (a.applicationName () + " - " + temp_name);
202 }
203
204 if (parser.isSet (test_option))
205 {
206 a.setApplicationName (a.applicationName () + " - test");
207 }
208
209 multiple = true;
210 }
211
212 // now we have the application name we can open the logging and settings
213 WSJTXLogging lg;
214 LOG_INFO (program_title (revision ()) << " - Program startup");
215 MultiSettings multi_settings {parser.value (cfg_option)};
216
217 // find the temporary files path
218 QDir temp_dir {QStandardPaths::writableLocation (QStandardPaths::TempLocation)};
219 Q_ASSERT (temp_dir.exists ()); // sanity check
220
221 // disallow multiple instances with same instance key
222 QLockFile instance_lock {temp_dir.absoluteFilePath (a.applicationName () + ".lock")};
223 instance_lock.setStaleLockTime (0);
224 while (!instance_lock.tryLock ())
225 {
226 if (QLockFile::LockFailedError == instance_lock.error ())
227 {
228 auto button = MessageBox::query_message (nullptr
229 , "Another instance may be running"
230 , "try to remove stale lock file?"
231 , QString {}
232 , MessageBox::Yes | MessageBox::Retry | MessageBox::No
233 , MessageBox::Yes);
234 switch (button)
235 {
236 case MessageBox::Yes:
237 instance_lock.removeStaleLockFile ();
238 break;
239
240 case MessageBox::Retry:
241 break;
242
243 default:
244 throw std::runtime_error {"Multiple instances must have unique rig names"};
245 }
246 }
247 else
248 {
249 throw std::runtime_error {"Failed to access lock file"};
250 }
251 }
252
253 // load UI translations
254 L10nLoader l10n {&a, locale, parser.value (lang_option)};
255
256 // Create a unique writeable temporary directory in a suitable location
257 bool temp_ok {false};
258 QString unique_directory {ExceptionCatchingApplication::applicationName ()};
259 do
260 {
261 if (!temp_dir.mkpath (unique_directory)
262 || !temp_dir.cd (unique_directory))
263 {
264 MessageBox::critical_message (nullptr,
265 a.translate ("main", "Failed to create a temporary directory"),
266 a.translate ("main", "Path: \"%1\"").arg (temp_dir.absolutePath ()));
267 throw std::runtime_error {"Failed to create a temporary directory"};
268 }
269 if (!temp_dir.isReadable () || !(temp_ok = QTemporaryFile {temp_dir.absoluteFilePath ("test")}.open ()))
270 {
271 auto button = MessageBox::critical_message (nullptr,
272 a.translate ("main", "Failed to create a usable temporary directory"),
273 a.translate ("main", "Another application may be locking the directory"),
274 a.translate ("main", "Path: \"%1\"").arg (temp_dir.absolutePath ()),
275 MessageBox::Retry | MessageBox::Cancel);
276 if (MessageBox::Cancel == button)
277 {
278 throw std::runtime_error {"Failed to create a usable temporary directory"};
279 }
280 temp_dir.cdUp (); // revert to parent as this one is no good
281 }
282 }
283 while (!temp_ok);
284
285 SplashScreen splash;
286 {
287 // change this key if you want to force a new splash screen
288 // for a new version, the user will be able to re-disable it
289 // if they wish
290 // Z
291 QString splash_flag_name {"Splash_v2.5-1.10"};
292 if (multi_settings.common_value (splash_flag_name, true).toBool ())
293 {
294 QObject::connect (&splash, &SplashScreen::disabled, [&, splash_flag_name] {
295 multi_settings.set_common_value (splash_flag_name, false);
296 splash.close ();
297 });
298 splash.show ();
299 a.processEvents ();
300 }
301 }
302
303 // create writeable data directory if not already there
304 auto writeable_data_dir = QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)};
305 if (!writeable_data_dir.mkpath ("."))
306 {
307 MessageBox::critical_message (nullptr, a.translate ("main", "Failed to create data directory"),
308 a.translate ("main", "path: \"%1\"").arg (writeable_data_dir.absolutePath ()));
309 throw std::runtime_error {"Failed to create data directory"};
310 }
311
312 // set up SQLite database
313 if (!QSqlDatabase::drivers ().contains ("QSQLITE"))
314 {
315 throw std::runtime_error {"Failed to find SQLite Qt driver"};
316 }
317 auto db = QSqlDatabase::addDatabase ("QSQLITE");
318 db.setDatabaseName (writeable_data_dir.absoluteFilePath ("db.sqlite"));
319 if (!db.open ())
320 {
321 throw std::runtime_error {("Database Error: " + db.lastError ().text ()).toStdString ()};
322 }
323
324 // better performance traded for a risk of d/b corruption
325 // on system crash or application crash
326 // db.exec ("PRAGMA synchronous=OFF"); // system crash risk
327 // db.exec ("PRAGMA journal_mode=MEMORY"); // application crash risk
328 db.exec ("PRAGMA locking_mode=EXCLUSIVE");
329
330 int result;
331 auto const& original_style_sheet = a.styleSheet ();
332 do
333 {
334 // dump settings
335 auto sys_lg = sys::get ();
336 if (auto rec = sys_lg.open_record
337 (
338 boost::log::keywords::severity = boost::log::trivial::trace)
339 )
340 {
341 boost::log::record_ostream strm (rec);
342 strm << "++++++++++++++++++++++++++++ Settings ++++++++++++++++++++++++++++\n";
343 for (auto const& key: multi_settings.settings ()->allKeys ())
344 {
345 if (!key.contains (QRegularExpression {"^MultiSettings/[^/]*/"}))
346 {
347 auto const& value = multi_settings.settings ()->value (key);
348 if (value.canConvert<QVariantList> ())
349 {
350 auto const sequence = value.value<QSequentialIterable> ();
351 strm << key << ":\n";
352 for (auto const& item: sequence)
353 {
354 strm << "\t";
355 safe_stream_QVariant (strm, item);
356 strm << '\n';
357 }
358 }
359 else
360 {
361 strm << key << ": ";
362 safe_stream_QVariant (strm, value);
363 strm << '\n';
364 }
365 }
366 }
367 strm << "---------------------------- Settings ----------------------------\n";
368 strm.flush ();
369 sys_lg.push_record (boost::move (rec));
370 }
371
372 // Create and initialize shared memory segment
373 // Multiple instances: use rig_name as shared memory key
374 mem_jt9.setKey(a.applicationName ());
375
376 // try and shut down any orphaned jt9 process
377 for (int i = 3; i; --i) // three tries to close old jt9
378 {
379 if (mem_jt9.attach ()) // shared memory presence implies
380 // orphaned jt9 sub-process
381 {
382 dec_data_t * dd = reinterpret_cast<dec_data_t *> (mem_jt9.data());
383 mem_jt9.lock ();
384 dd->ipc[1] = 999; // tell jt9 to shut down
385 mem_jt9.unlock ();
386 mem_jt9.detach (); // start again
387 }
388 else
389 {
390 break; // good to go
391 }
392 QThread::sleep (1); // wait for jt9 to end
393 }
394 if (!mem_jt9.attach ())
395 {
396 if (!mem_jt9.create (sizeof (dec_data)))
397 {
398 splash.hide ();
399 MessageBox::critical_message (nullptr, a.translate ("main", "Shared memory error"),
400 a.translate ("main", "Unable to create shared memory segment"));
401 throw std::runtime_error {"Shared memory error"};
402 }
403 LOG_INFO ("shmem size: " << mem_jt9.size ());
404 }
405 else
406 {
407 splash.hide ();
408 MessageBox::critical_message (nullptr, a.translate ("main", "Sub-process error"),
409 a.translate ("main", "Failed to close orphaned jt9 process"));
410 throw std::runtime_error {"Sub-process error"};
411 }
412 mem_jt9.lock ();
413 memset(mem_jt9.data(),0,sizeof(struct dec_data)); //Zero all decoding params in shared memory
414 mem_jt9.unlock ();
415
416 unsigned downSampleFactor;
417 {
418 SettingsGroup {multi_settings.settings (), "Tune"};
419
420 // deal with Windows Vista and earlier input audio rate
421 // converter problems
422 downSampleFactor = multi_settings.settings ()->value ("Audio/DisableInputResampling",
423 #if defined (Q_OS_WIN)
424 // default to true for
425 // Windows Vista and older
426 QSysInfo::WV_VISTA >= QSysInfo::WindowsVersion ? true : false
427 #else
428 false
429 #endif
430 ).toBool () ? 1u : 4u;
431 }
432
433 // run the application UI
434 MainWindow w(temp_dir, multiple, &multi_settings, &mem_jt9, downSampleFactor, &splash, env);
435 w.show();
436 splash.raise ();
437 QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit()));
438 result = a.exec();
439
440 // ensure config switches start with the right style sheet
441 a.setStyleSheet (original_style_sheet);
442 }
443 while (!result && !multi_settings.exit ());
444
445 // clean up lazily initialized resources
446 {
447 int nfft {-1};
448 int ndim {1};
449 int isign {1};
450 int iform {1};
451 // free FFT plan resources
452 four2a_ (nullptr, &nfft, &ndim, &isign, &iform, 0);
453 }
454 fftwf_forget_wisdom ();
455 fftwf_cleanup ();
456
457 temp_dir.removeRecursively (); // clean up temp files
458 return result;
459 }
460 catch (std::exception const& e)
461 {
462 MessageBox::critical_message (nullptr, "Fatal error", e.what ());
463 std::cerr << "Error: " << e.what () << '\n';
464 }
465 catch (...)
466 {
467 MessageBox::critical_message (nullptr, "Unexpected fatal error");
468 std::cerr << "Unexpected fatal error\n";
469 throw; // hoping the runtime might tell us more about the exception
470 }
471 return -1;
472 }
473