js8call/JS8_Main/main.cpp
Chris-AC9KH 356bdc0f50 Code cleanup - move loose source and header files in source tree to group folders
Rename all header files with .h extension
Fix function declaration in HamlibTransceiver.h that overrode member function but was not marked override
Remove unused variable in Modulator.cpp
Remove unused files from source tree
Remove duplicate LazyFillComboBox files
Reformat codebase to LLVM C++ standard
2026-01-02 16:46:38 -06:00

290 lines
10 KiB
C++

#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
#include <fftw3.h>
#include <locale.h>
#include <QApplication>
#include <QDateTime>
#include <QDir>
#include <QLibraryInfo>
#include <QLockFile>
#include <QLoggingCategory>
#include <QObject>
#include <QRegularExpression>
#include <QSettings>
#include <QStack>
#include <QStandardPaths>
#include <QStringList>
#include <QSysInfo>
#if QT_VERSION >= 0x050200
#include <QCommandLineOption>
#include <QCommandLineParser>
#endif
#include "FrequencyList.h"
#include "JS8MessageBox.h"
#include "JS8_Include/SettingsGroup.h"
#include "JS8_Include/commons.h"
#include "JS8_UI/mainwindow.h"
#include "MetaDataRegistry.h"
#include "MultiSettings.h"
#include "Radio.h"
#include "TraceFile.h"
#include "revision_utils.h"
#include "DriftingDateTime.h"
Q_DECLARE_LOGGING_CATEGORY(main_js8)
namespace {
class MessageTimestamper {
public:
MessageTimestamper() {
prior_handlers_.push(qInstallMessageHandler(message_handler));
}
~MessageTimestamper() {
if (prior_handlers_.size())
qInstallMessageHandler(prior_handlers_.pop());
}
private:
static void message_handler(QtMsgType type,
QMessageLogContext const &context,
QString const &msg) {
QtMessageHandler handler{prior_handlers_.top()};
if (handler) {
handler(type, context,
DriftingDateTime::currentDateTimeUtc().toString(
"yy-MM-ddTHH:mm:ss.zzzZ: ") +
msg);
}
}
static QStack<QtMessageHandler> prior_handlers_;
};
QStack<QtMessageHandler> MessageTimestamper::prior_handlers_;
} // namespace
int main(int argc, char *argv[]) {
// Add timestamps to all debug messages
MessageTimestamper message_timestamper;
// make the Qt type magic happen
Radio::register_types();
register_types();
QApplication a(argc, argv);
try {
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("JS8Call");
a.setApplicationVersion(version());
#if QT_VERSION >= 0x050200
QCommandLineParser parser;
parser.setApplicationDescription("\n" PROJECT_SUMMARY_DESCRIPTION);
auto help_option = parser.addHelpOption();
auto version_option = parser.addVersionOption();
QCommandLineOption output_option(QStringList() << "o" << "output",
"Write debug statements into <file>.",
"file");
parser.addOption(output_option);
// support for multiple instances running from a single installation
QCommandLineOption rig_option(
QStringList{} << "r" << "rig-name",
a.translate("main",
"Where <rig-name> is for multi-instance support."),
a.translate("main", "rig-name"));
parser.addOption(rig_option);
// support for start up configuration
QCommandLineOption cfg_option(
QStringList{} << "c" << "config",
a.translate("main", "Where <configuration> is an existing one."),
a.translate("main", "configuration"));
parser.addOption(cfg_option);
QCommandLineOption test_option(
QStringList{} << "test-mode",
a.translate("main", "Writable files in test location. Use with "
"caution, for testing only."));
parser.addOption(test_option);
if (!parser.parse(a.arguments())) {
std::cerr << parser.errorText().toLocal8Bit().data() << std::endl;
return -1;
} else {
if (parser.isSet(help_option)) {
parser.showHelp(-1);
return 0;
} else if (parser.isSet(version_option)) {
parser.showVersion();
return 0;
}
}
if (parser.isSet(output_option)) {
new TraceFile(parser.value(output_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);
while (!instance_lock.tryLock()) {
if (QLockFile::LockFailedError == instance_lock.error()) {
switch (JS8MessageBox::query_message(
nullptr,
a.translate("main", "Another instance may be running"),
a.translate("main", "try to remove stale lock file?"),
QString{},
JS8MessageBox::Yes | JS8MessageBox::Retry |
JS8MessageBox::No,
JS8MessageBox::Yes)) {
case JS8MessageBox::Yes:
instance_lock.removeStaleLockFile();
break;
case JS8MessageBox::Retry:
break;
default:
throw std::runtime_error{
"Multiple instances must have unique rig names"};
}
} else {
throw std::runtime_error{"Failed to access lock file"};
}
}
#endif
#if WSJT_QDEBUG_TO_FILE
// Open a trace file
TraceFile trace_file{
temp_dir.absoluteFilePath(a.applicationName() + "_trace.log")};
qCDebug(main_js8) << program_title() + " - 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)) {
JS8MessageBox::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 = JS8MessageBox::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()),
JS8MessageBox::Retry | JS8MessageBox::Cancel);
if (JS8MessageBox::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);
int result;
do {
#if WSJT_QDEBUG_TO_FILE
// announce to trace file and dump settings
qCDebug(main_js8) << "++++++++++++++++++++++++++++ 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>();
qCDebug(main_js8).nospace() << key << ": ";
for (auto const &item : sequence) {
qCDebug(main_js8).nospace() << '\t' << item;
}
} else {
qCDebug(main_js8).nospace() << key << ": " << value;
}
}
qCDebug(main_js8) << "---------------------------- Settings "
"----------------------------";
#endif
// run the application UI
MainWindow w(program_version(), temp_dir, multiple,
&multi_settings);
w.show();
result = a.exec();
} while (!result && !multi_settings.exit());
fftwf_forget_wisdom();
fftwf_cleanup();
temp_dir.removeRecursively(); // clean up temp files
return result;
} catch (std::exception const &e) {
JS8MessageBox::critical_message(nullptr, "Fatal error", e.what());
std::cerr << "Error: " << e.what() << '\n';
} catch (...) {
JS8MessageBox::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;
}
Q_LOGGING_CATEGORY(main_js8, "main.js8", QtWarningMsg)