More flexible execution of the user's hardware controller

Updated ways to implement a  user defined hardware controller which is
executed just after band changes during WSPR band hopping operation.

Allows the user_hardware executable to  be located in any directory on
the PATH environment variable. On Windows any file extension listed on
the PATHEXT  environment variable may  be used, the first  match using
PATH and  PATHEXT will  be executed.  On Windows  this is  achieved by
using CMD.EXE with a '/C' command  line flag, i.e. the user's hardware
controller is executed like this:

CMD.EXE /C user_hardware nn

where 'nn' is the new band as an integer in meters.

On non-Windows  systems the  user's executable  will be  run if  it is
found on the  directories specified by the  PATH environment variable,
and it is executable, i.e. it is equivalent to something like:

/bin/sh -c user_hardware nn

where 'nn' is the new band as an integer in meters.

In  all cases  the user_hardware  controller should  exit with  a zero
status, otherwise it have been deemed to have failed. On Windows avoid
an exit status of  one as that is utilized by  CMD.EXE to indicate the
file was not found, which WSJT-X ignores silently.

This change means the prior need to put the user's hardware controller
into   a  WSJT-X   installation  directory   like  /usr/local/bin   or
C:\WSJT\wsjtx\bin is no longer necessary.
This commit is contained in:
Bill Somerville 2020-06-26 21:35:29 +01:00
parent a24f402655
commit 27b4f2939a
No known key found for this signature in database
GPG Key ID: D864B06D1E81618F
2 changed files with 54 additions and 28 deletions

View File

@ -662,7 +662,12 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
#endif
connect(&proc_jt9, static_cast<void (QProcess::*) (int, QProcess::ExitStatus)> (&QProcess::finished),
[this] (int exitCode, QProcess::ExitStatus status) {
subProcessFailed (&proc_jt9, exitCode, status);
if (subProcessFailed (&proc_jt9, exitCode, status))
{
m_valid = false; // ensures exit if still
// constructing
QTimer::singleShot (0, this, SLOT (close ()));
}
});
connect(&p1, &QProcess::started, [this] () {
showStatusMessage (QString {"Started: %1 \"%2\""}.arg (p1.program ()).arg (p1.arguments ().join (QLatin1String {"\" \""})));
@ -680,25 +685,42 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
#endif
connect(&p1, static_cast<void (QProcess::*) (int, QProcess::ExitStatus)> (&QProcess::finished),
[this] (int exitCode, QProcess::ExitStatus status) {
subProcessFailed (&p1, exitCode, status);
if (subProcessFailed (&p1, exitCode, status))
{
m_valid = false; // ensures exit if still
// constructing
QTimer::singleShot (0, this, SLOT (close ()));
}
});
#if QT_VERSION < QT_VERSION_CHECK (5, 6, 0)
connect(&p3, static_cast<void (QProcess::*) (QProcess::ProcessError)> (&QProcess::error),
[this] (QProcess::ProcessError error) {
subProcessError (&p3, error);
});
#else
connect(&p3, &QProcess::errorOccurred, [this] (QProcess::ProcessError error) {
subProcessError (&p3, error);
});
#endif
#if not defined(Q_OS_WIN)
if (QProcess::FailedToStart != error)
#endif
{
subProcessError (&p3, error);
}
});
connect(&p3, &QProcess::started, [this] () {
showStatusMessage (QString {"Started: %1 \"%2\""}.arg (p3.program ()).arg (p3.arguments ().join (QLatin1String {"\" \""})));
});
connect(&p3, static_cast<void (QProcess::*) (int, QProcess::ExitStatus)> (&QProcess::finished),
[this] (int exitCode, QProcess::ExitStatus status) {
subProcessFailed (&p3, exitCode, status);
#if defined(Q_OS_WIN)
// We forgo detecting user_hardware failures with exit
// code 1 on Windows. This is because we use CMD.EXE to
// run the executable. CMD.EXE returns exit code 1 when it
// can't find the target executable.
if (exitCode != 1) // CMD.EXE couldn't find file to execute
#endif
{
subProcessFailed (&p3, exitCode, status);
}
});
// hook up save WAV file exit handling
@ -2335,7 +2357,7 @@ void MainWindow::setup_status_bar (bool vhf)
}
}
void MainWindow::subProcessFailed (QProcess * process, int exit_code, QProcess::ExitStatus status)
bool MainWindow::subProcessFailed (QProcess * process, int exit_code, QProcess::ExitStatus status)
{
if (m_valid && (exit_code || QProcess::NormalExit != status))
{
@ -2352,9 +2374,9 @@ void MainWindow::subProcessFailed (QProcess * process, int exit_code, QProcess::
, tr ("Running: %1\n%2")
.arg (process->program () + ' ' + arguments.join (' '))
.arg (QString {process->readAllStandardError()}));
QTimer::singleShot (0, this, SLOT (close ()));
m_valid = false; // ensures exit if still constructing
return true;
}
return false;
}
void MainWindow::subProcessError (QProcess * process, QProcess::ProcessError)
@ -2372,8 +2394,8 @@ void MainWindow::subProcessError (QProcess * process, QProcess::ProcessError)
, tr ("Running: %1\n%2")
.arg (process->program () + ' ' + arguments.join (' '))
.arg (process->errorString ()));
QTimer::singleShot (0, this, SLOT (close ()));
m_valid = false; // ensures exit if still constructing
QTimer::singleShot (0, this, SLOT (close ()));
}
}
@ -7895,22 +7917,25 @@ void MainWindow::WSPR_scheduling ()
if (hop_data.frequencies_index_ >= 0) { // new band
ui->bandComboBox->setCurrentIndex (hop_data.frequencies_index_);
on_bandComboBox_activated (hop_data.frequencies_index_);
QStringList prefixes {".bat", ".cmd", ".exe", ""};
QString target;
for (auto const& prefix : prefixes)
{
target = QDir {m_appDir}.absoluteFilePath (QLatin1String {"user_hardware"});
QFileInfo f {target + prefix};
if (f.isExecutable ()) {
break;
}
}
if (target.size ())
{
// Execute user's hardware controller
p3.start(QDir::toNativeSeparators (target)
, QStringList {m_config.bands ()->find (m_freqNominal).remove ('m')});
}
// Execute user's hardware controller
auto const& band = m_config.bands ()->find (m_freqNominal).remove ('m');
#if defined(Q_OS_WIN)
// On windows we use CMD.EXE to find and execute the
// user_hardware executable. This means that the first matching
// file extension on the PATHEXT environment variable found on
// the PATH environment variable path list. This give maximum
// flexibility for users to write user_hardware in their
// language of choice, and place the file anywhere on the PATH
// environment variable. Equivalent to typing user_hardware
// without any path or extension at the CMD.EXE prompt.
p3.start("CMD", QStringList {QLatin1String {"/C"}, QLatin1String {"user_hardware"}, band});
#else
// On non-Windows systems we expect the user_hardware executable
// to be anywhere in the paths specified in the PATH environment
// variable path list, and executable. Equivalent to typing
// user_hardware without any path at the shell prompt.
p3.start(QLatin1String {"user_hardware"}, QStringList {band});
#endif
// Produce a short tuneup signal
m_tuneup = false;

View File

@ -3,6 +3,7 @@
#define MAINWINDOW_H
#include <QMainWindow>
#include <QByteArray>
#include <QString>
#include <QStringList>
#include <QLabel>
@ -749,7 +750,7 @@ private:
void rm_tb4(QString houndCall);
void read_wav_file (QString const& fname);
void decodeDone ();
void subProcessFailed (QProcess *, int exit_code, QProcess::ExitStatus);
bool subProcessFailed (QProcess *, int exit_code, QProcess::ExitStatus);
void subProcessError (QProcess *, QProcess::ProcessError);
void statusUpdate () const;
void update_watchdog_label ();