ProjectExplorer: Change IDevice to use Aspects

Change-Id: I1619426fcbaf5ca909abe7d57d4289de407c2830
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2025-04-23 10:48:51 +02:00
parent ea06a8568e
commit f3f56edbad
16 changed files with 372 additions and 410 deletions

View File

@@ -141,7 +141,7 @@ void QdbDevice::setupDefaultNetworkSettings(const QString &host)
parameters.setPort(22);
parameters.setTimeout(10);
parameters.setAuthenticationType(SshParameters::AuthenticationTypeAll);
setSshParameters(parameters);
setDefaultSshParameters(parameters);
}
// QdbDeviceWizard

View File

@@ -380,7 +380,7 @@ ExecutableItem debugServerRecipe(const Storage<DebuggerData> &storage, const Sin
const auto port = runControl->debugChannel().port();
cmd.addArg(QString(":%1").arg(port));
if (runControl->device()->extraData(ProjectExplorer::Constants::SSH_FORWARD_DEBUGSERVER_PORT).toBool()) {
if (runControl->device()->sshForwardDebugServerPort()) {
QVariantHash extraData;
extraData[RemoteLinux::Constants::SshForwardPort] = port;
extraData[RemoteLinux::Constants::DisableSharing] = true;

View File

@@ -30,6 +30,7 @@
#include <QDateTime>
#include <QReadWriteLock>
#include <QStandardItem>
#include <QString>
/*!
@@ -115,6 +116,9 @@ const char HostKeyCheckingKey[] = "HostKeyChecking";
const char DebugServerKey[] = "DebugServerKey";
const char QmlRuntimeKey[] = "QmlsceneKey";
const char SshForwardDebugServerPortKey[] = "SshForwardDebugServerPort";
const char LinkDeviceKey[] = "LinkDevice";
using AuthType = SshParameters::AuthenticationType;
const AuthType DefaultAuthType = SshParameters::AuthenticationTypeAll;
const IDevice::MachineType DefaultMachineType = IDevice::Hardware;
@@ -139,16 +143,14 @@ public:
Utils::SynchronizedValue<SshParameters> sshParameters;
PortList freePorts;
QList<Icon> deviceIcons;
QList<IDevice::DeviceAction> deviceActions;
Store extraData;
IDevice::OpenTerminal openTerminal;
Utils::StringAspect displayName;
Utils::FilePathAspect debugServerPath;
Utils::FilePathAspect qmlRunCommand;
SshParametersAspectContainer sshParametersAspectContainer;
bool isTesting = false;
};
@@ -172,12 +174,49 @@ IDevice::IDevice()
{
setAutoApply(false);
registerAspect(&d->sshParametersAspectContainer);
connect(&d->sshParametersAspectContainer, &AspectContainer::applied, this, [this]() {
*d->sshParameters.writeLocked() = d->sshParametersAspectContainer.sshParameters();
});
registerAspect(&d->displayName);
d->displayName.setSettingsKey(DisplayNameKey);
d->displayName.setDisplayStyle(StringAspect::DisplayStyle::LineEditDisplay);
// allowEmptyCommand.setSettingsKey() intentionally omitted, this is not persisted.
sshForwardDebugServerPort.setSettingsKey(SshForwardDebugServerPortKey);
sshForwardDebugServerPort.setLabelText(Tr::tr("Use SSH port forwarding for debugging"));
sshForwardDebugServerPort.setToolTip(
Tr::tr("Enable debugging on remote targets which cannot expose gdbserver ports.\n"
"The ssh tunneling is used to map the remote gdbserver port to localhost.\n"
"The local and remote ports are determined automatically."));
sshForwardDebugServerPort.setDefaultValue(false);
sshForwardDebugServerPort.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox);
linkDevice.setSettingsKey(LinkDeviceKey);
linkDevice.setLabelText(Tr::tr("Access via:"));
linkDevice.setToolTip(Tr::tr("Select the device to connect through."));
linkDevice.setDefaultValue("Direct");
linkDevice.setComboBoxEditable(false);
linkDevice.setFillCallback([this](const StringSelectionAspect::ResultCallback &cb) {
auto dm = DeviceManager::instance();
QList<QStandardItem *> items;
auto defaultItem = new QStandardItem(Tr::tr("Direct"));
defaultItem->setData("direct");
items.append(defaultItem);
for (int i = 0, n = dm->deviceCount(); i < n; ++i) {
const auto device = dm->deviceAt(i);
if (device->id() == this->id())
continue;
QStandardItem *newItem = new QStandardItem(device->displayName());
newItem->setData(device->id().toSetting());
items.append(newItem);
}
cb(items);
});
auto validateDisplayName = [](const QString &old, const QString &newValue) -> Result<> {
if (old == newValue)
return ResultOk;
@@ -205,11 +244,27 @@ IDevice::IDevice()
return newValue;
});
registerAspect(&d->debugServerPath);
d->debugServerPath.setSettingsKey(DebugServerKey);
debugServerPathAspect.setSettingsKey(DebugServerKey);
debugServerPathAspect.setLabelText(Tr::tr("GDB server executable:"));
debugServerPathAspect.setToolTip(Tr::tr("The GDB server executable to use on the device."));
debugServerPathAspect.setPlaceHolderText(Tr::tr("Leave empty to look up executable in $PATH"));
debugServerPathAspect.setHistoryCompleter("GdbServer");
debugServerPathAspect.setAllowPathFromDevice(true);
debugServerPathAspect.setExpectedKind(PathChooser::ExistingCommand);
registerAspect(&d->qmlRunCommand);
d->qmlRunCommand.setSettingsKey(QmlRuntimeKey);
qmlRunCommandAspect.setSettingsKey(QmlRuntimeKey);
qmlRunCommandAspect.setLabelText(Tr::tr("QML runtime executable:"));
qmlRunCommandAspect.setToolTip(Tr::tr("The QML runtime executable to use on the device."));
qmlRunCommandAspect.setPlaceHolderText(Tr::tr("Leave empty to look up executable in $PATH"));
qmlRunCommandAspect.setHistoryCompleter("QmlRuntime");
qmlRunCommandAspect.setAllowPathFromDevice(true);
qmlRunCommandAspect.setExpectedKind(PathChooser::ExistingCommand);
freePortsAspect.setSettingsKey(PortsSpecKey);
freePortsAspect.setLabelText(Tr::tr("Free ports:"));
freePortsAspect.setToolTip(
Tr::tr("You can enter lists and ranges like this: '1024,1026-1028,1030'."));
freePortsAspect.setHistoryCompleter("PortRange");
}
IDevice::~IDevice() = default;
@@ -258,22 +313,22 @@ FilePath IDevice::filePath(const QString &pathOnDevice) const
FilePath IDevice::debugServerPath() const
{
return d->debugServerPath();
return debugServerPathAspect();
}
void IDevice::setDebugServerPath(const FilePath &path)
{
d->debugServerPath.setValue(path);
debugServerPathAspect.setValue(path);
}
FilePath IDevice::qmlRunCommand() const
{
return d->qmlRunCommand();
return qmlRunCommandAspect();
}
void IDevice::setQmlRunCommand(const FilePath &path)
{
d->qmlRunCommand.setValue(path);
qmlRunCommandAspect.setValue(path);
}
bool IDevice::handlesFile(const FilePath &filePath) const
@@ -492,12 +547,34 @@ Id IDevice::idFromMap(const Store &map)
return Id::fromSetting(map.value(IdKey));
}
// Backwards compatibility: Pre 17.0 a bunch of settings were stored in the extra data
namespace {
static const char LinkDevice[] = "RemoteLinux.LinkDevice";
static const char SSH_FORWARD_DEBUGSERVER_PORT[] = "RemoteLinux.SshForwardDebugServerPort";
static void backwardsFromExtraData(IDevice *device, const Store &map)
{
if (map.contains(LinkDevice))
device->linkDevice.setValue(Id::fromSetting(map.value(LinkDevice)).toString());
if (map.contains(SSH_FORWARD_DEBUGSERVER_PORT))
device->sshForwardDebugServerPort.setValue(map.value(SSH_FORWARD_DEBUGSERVER_PORT).toBool());
}
static void backwardsToExtraData(const IDevice *const device, Store &map)
{
map.insert(LinkDevice, device->linkDevice());
map.insert(SSH_FORWARD_DEBUGSERVER_PORT, device->sshForwardDebugServerPort());
}
} // namespace
/*!
Restores a device object from a serialized state as written by toMap().
If subclasses override this to restore additional state, they must call the
base class implementation.
*/
void IDevice::fromMap(const Store &map)
{
AspectContainer::fromMap(map);
@@ -509,33 +586,32 @@ void IDevice::fromMap(const Store &map)
d->id = newId();
d->origin = static_cast<Origin>(map.value(OriginKey, ManuallyAdded).toInt());
d->sshParameters.write([&map](SshParameters &ssh) {
ssh.setHost(map.value(HostKey).toString());
ssh.setPort(map.value(SshPortKey, 22).toInt());
ssh.setUserName(map.value(UserNameKey).toString());
// Pre-4.9, the authentication enum used to have more values
const int storedAuthType = map.value(AuthKey, DefaultAuthType).toInt();
const bool outdatedAuthType = storedAuthType > SshParameters::AuthenticationTypeSpecificKey;
ssh.setAuthenticationType(
outdatedAuthType ? SshParameters::AuthenticationTypeAll
: static_cast<AuthType>(storedAuthType));
ssh.setPrivateKeyFile(
FilePath::fromSettings(map.value(KeyFileKey, defaultPrivateKeyFilePath())));
ssh.setTimeout(map.value(TimeoutKey, DefaultTimeout).toInt());
ssh.setHostKeyCheckingMode(static_cast<SshHostKeyCheckingMode>(
map.value(HostKeyCheckingKey, SshHostKeyCheckingNone).toInt()));
});
QString portsSpec = map.value(PortsSpecKey).toString();
if (portsSpec.isEmpty())
portsSpec = "10000-10100";
d->freePorts = PortList::fromString(portsSpec);
d->machineType = static_cast<MachineType>(map.value(MachineTypeKey, DefaultMachineType).toInt());
d->version = map.value(VersionKey, 0).toInt();
d->extraData = storeFromVariant(map.value(ExtraDataKey));
backwardsFromExtraData(this, d->extraData);
SshParameters ssh;
ssh.setHost(map.value(HostKey).toString());
ssh.setPort(map.value(SshPortKey, 22).toInt());
ssh.setUserName(map.value(UserNameKey).toString());
// Pre-4.9, the authentication enum used to have more values
const int storedAuthType = map.value(AuthKey, DefaultAuthType).toInt();
const bool outdatedAuthType = storedAuthType > SshParameters::AuthenticationTypeSpecificKey;
ssh.setAuthenticationType(
outdatedAuthType ? SshParameters::AuthenticationTypeAll
: static_cast<AuthType>(storedAuthType));
ssh.setPrivateKeyFile(
FilePath::fromSettings(map.value(KeyFileKey, defaultPrivateKeyFilePath())));
ssh.setTimeout(map.value(TimeoutKey, DefaultTimeout).toInt());
ssh.setHostKeyCheckingMode(static_cast<SshHostKeyCheckingMode>(
map.value(HostKeyCheckingKey, SshHostKeyCheckingNone).toInt()));
d->sshParametersAspectContainer.setSshParameters(ssh);
}
/*!
@@ -554,21 +630,21 @@ void IDevice::toMap(Store &map) const
map.insert(OriginKey, d->origin);
map.insert(MachineTypeKey, d->machineType);
d->sshParameters.read([&map](const auto &ssh) {
map.insert(HostKey, ssh.host());
map.insert(SshPortKey, ssh.port());
map.insert(UserNameKey, ssh.userName());
map.insert(AuthKey, ssh.authenticationType());
map.insert(KeyFileKey, ssh.privateKeyFile().toSettings());
map.insert(TimeoutKey, ssh.timeout());
map.insert(HostKeyCheckingKey, ssh.hostKeyCheckingMode());
});
map.insert(PortsSpecKey, d->freePorts.toString());
map.insert(VersionKey, d->version);
map.insert(ExtraDataKey, variantFromStore(d->extraData));
Store extraData = d->extraData;
backwardsToExtraData(this, extraData);
map.insert(ExtraDataKey, variantFromStore(extraData));
SshParameters ssh = d->sshParametersAspectContainer.sshParameters();
map.insert(HostKey, ssh.host());
map.insert(SshPortKey, ssh.port());
map.insert(UserNameKey, ssh.userName());
map.insert(AuthKey, ssh.authenticationType());
map.insert(KeyFileKey, ssh.privateKeyFile().toSettings());
map.insert(TimeoutKey, ssh.timeout());
map.insert(HostKeyCheckingKey, ssh.hostKeyCheckingMode());
}
IDevice::Ptr IDevice::clone() const
@@ -639,9 +715,23 @@ SshParameters IDevice::sshParameters() const
return *d->sshParameters.readLocked();
}
void IDevice::setSshParameters(const SshParameters &sshParameters)
void IDevice::setDefaultSshParameters(const SshParameters &sshParameters)
{
*d->sshParameters.writeLocked() = sshParameters;
QTC_ASSERT(QThread::currentThread() == qApp->thread(),
return); // This is not thread-safe.
sshParametersAspectContainer().host.setDefaultValue(sshParameters.host());
sshParametersAspectContainer().port.setDefaultValue(sshParameters.port());
sshParametersAspectContainer().userName.setDefaultValue(sshParameters.userName());
sshParametersAspectContainer().privateKeyFile.setDefaultPathValue(
sshParameters.privateKeyFile());
sshParametersAspectContainer().timeout.setDefaultValue(sshParameters.timeout());
sshParametersAspectContainer().authenticationType.setDefaultValue(
sshParameters.authenticationType());
sshParametersAspectContainer().hostKeyCheckingMode.setDefaultValue(
sshParameters.hostKeyCheckingMode());
*d->sshParameters.writeLocked() = sshParametersAspectContainer().sshParameters();
}
QUrl IDevice::toolControlChannel(const ControlChannelHint &) const
@@ -654,12 +744,12 @@ QUrl IDevice::toolControlChannel(const ControlChannelHint &) const
void IDevice::setFreePorts(const PortList &freePorts)
{
d->freePorts = freePorts;
freePortsAspect.setPortList(freePorts);
}
PortList IDevice::freePorts() const
{
return d->freePorts;
return freePortsAspect.portList();
}
IDevice::MachineType IDevice::machineType() const
@@ -813,6 +903,13 @@ QVariant DeviceConstRef::extraData(Id kind) const
return device->extraData(kind);
}
Id DeviceConstRef::linkDeviceId() const
{
const IDevice::ConstPtr device = m_constDevice.lock();
QTC_ASSERT(device, return {});
return Id::fromString(device->linkDevice.value());
}
FilePath DeviceConstRef::filePath(const QString &pathOnDevice) const
{
const IDevice::ConstPtr device = m_constDevice.lock();
@@ -842,7 +939,12 @@ void DeviceRef::setSshParameters(const SshParameters &params)
{
const IDevice::Ptr device = m_mutableDevice.lock();
QTC_ASSERT(device, return);
device->setSshParameters(params);
device->setDefaultSshParameters(params);
}
SshParametersAspectContainer &IDevice::sshParametersAspectContainer() const
{
return d->sshParametersAspectContainer;
}
} // namespace ProjectExplorer

View File

@@ -45,6 +45,7 @@ class FileTransferInterface;
class FileTransferSetupData;
class Kit;
class SshParameters;
class SshParametersAspectContainer;
class Target;
class Task;
@@ -157,7 +158,9 @@ public:
static QString defaultPublicKeyFilePath();
SshParameters sshParameters() const;
void setSshParameters(const SshParameters &sshParameters);
void setDefaultSshParameters(const SshParameters &sshParameters);
SshParametersAspectContainer &sshParametersAspectContainer() const;
enum ControlChannelHint { QmlControlChannel };
virtual QUrl toolControlChannel(const ControlChannelHint &) const;
@@ -186,8 +189,6 @@ public:
Utils::Result<> openTerminal(const Utils::Environment &env,
const Utils::FilePath &workingDir) const;
Utils::BoolAspect allowEmptyCommand{this};
bool isWindowsDevice() const { return osType() == Utils::OsTypeWindows; }
bool isLinuxDevice() const { return osType() == Utils::OsTypeLinux; }
bool isMacDevice() const { return osType() == Utils::OsTypeMac; }
@@ -219,6 +220,14 @@ public:
void doApply() const;
public:
Utils::BoolAspect allowEmptyCommand{this};
Utils::StringSelectionAspect linkDevice{this};
Utils::BoolAspect sshForwardDebugServerPort{this};
Utils::FilePathAspect debugServerPathAspect{this};
Utils::FilePathAspect qmlRunCommandAspect{this};
Utils::PortListAspect freePortsAspect{this};
protected:
IDevice();
@@ -257,6 +266,7 @@ public:
SshParameters sshParameters() const;
Utils::FilePath filePath(const QString &pathOnDevice) const;
QVariant extraData(Utils::Id kind) const;
Utils::Id linkDeviceId() const;
private:
std::weak_ptr<const IDevice> m_constDevice;

View File

@@ -3,6 +3,7 @@
#include "sshparameters.h"
#include "../projectexplorertr.h"
#include "sshsettings.h"
#include <utils/environment.h>
@@ -59,7 +60,7 @@ QStringList SshParameters::connectionOptions(const FilePath &binary) const
args << "-o" << "User=" + userName();
const bool keyOnly = m_authenticationType == SshParameters::AuthenticationTypeSpecificKey;
if (keyOnly)
if (keyOnly && m_privateKeyFile.isReadableFile())
args << "-o" << "IdentitiesOnly=yes" << "-i" << m_privateKeyFile.path();
const QString batchModeEnabled = (keyOnly || SshSettings::askpassFilePath().isEmpty())
@@ -216,4 +217,90 @@ void printSetupHelp()
} // namespace SshTest
#endif
void SshParametersAspectContainer::setSshParameters(const SshParameters &params)
{
QTC_ASSERT(QThread::currentThread() == thread(), return);
host.setVolatileValue(params.host());
port.setVolatileValue(params.port());
userName.setVolatileValue(params.userName());
privateKeyFile.setVolatileValue(params.privateKeyFile().toUserOutput());
timeout.setVolatileValue(params.timeout());
authenticationType.setVolatileValue(params.authenticationType());
hostKeyCheckingMode.setVolatileValue(params.hostKeyCheckingMode());
privateKeyFile.setEnabled(
params.authenticationType() == SshParameters::AuthenticationTypeSpecificKey);
// This will emit the applied signal which the IDevice uses to update the ssh parameters.
apply();
}
SshParameters SshParametersAspectContainer::sshParameters() const
{
QTC_ASSERT(QThread::currentThread() == thread(), return SshParameters());
SshParameters params;
params.setHost(host.expandedValue());
params.setPort(port.value());
params.setUserName(userName.expandedValue());
params.setPrivateKeyFile(privateKeyFile.expandedValue());
params.setTimeout(timeout.value());
params.setAuthenticationType(authenticationType.value());
params.setHostKeyCheckingMode(hostKeyCheckingMode.value());
return params;
}
SshParametersAspectContainer::SshParametersAspectContainer()
{
authenticationType.setDefaultValue(SshParameters::AuthenticationTypeAll);
authenticationType.setDisplayStyle(SelectionAspect::DisplayStyle::RadioButtons);
authenticationType
.addOption(Tr::tr("Default"), Tr::tr("Use all available authentication methods"));
authenticationType
.addOption(Tr::tr("Specific &key"), Tr::tr("Use only the specified private key"));
authenticationType.setToolTip(Tr::tr("Select the authentication method to use"));
authenticationType.setLabelText(Tr::tr("Authentication type:"));
hostKeyCheckingMode.setToolTip(Tr::tr("The device's SSH host key checking mode"));
hostKeyCheckingMode.setLabelText(Tr::tr("Host key check:"));
hostKeyCheckingMode.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox);
hostKeyCheckingMode.addOption("None", Tr::tr("No host key checking"));
hostKeyCheckingMode.addOption("Allow No Match", Tr::tr("Allow host key checking"));
hostKeyCheckingMode.addOption("Strict", Tr::tr("Strict host key checking"));
host.setDisplayStyle(StringAspect::DisplayStyle::LineEditDisplay);
host.setPlaceHolderText(Tr::tr("Host name or IP address"));
host.setToolTip(Tr::tr("The device's host name or IP address"));
host.setHistoryCompleter("HostName");
host.setLabelText(Tr::tr("Host name:"));
userName.setDisplayStyle(StringAspect::DisplayStyle::LineEditDisplay);
userName.setPlaceHolderText(Tr::tr("User name"));
userName.setToolTip(Tr::tr("The device's SSH user name"));
userName.setHistoryCompleter("UserName");
userName.setLabelText(Tr::tr("User name:"));
port.setDefaultValue(22);
port.setRange(1, 65535);
port.setToolTip(Tr::tr("The device's SSH port number"));
port.setLabelText(Tr::tr("SSH port:"));
privateKeyFile.setPlaceHolderText(Tr::tr("Private key file"));
privateKeyFile.setToolTip(Tr::tr("The device's private key file"));
privateKeyFile.setLabelText(Tr::tr("Private key file:"));
privateKeyFile.setHistoryCompleter("KeyFile");
privateKeyFile.setEnabled(
authenticationType.volatileValue() == SshParameters::AuthenticationTypeSpecificKey);
connect(&authenticationType, &SelectionAspect::volatileValueChanged, this, [this]() {
privateKeyFile.setEnabled(
authenticationType.volatileValue() == SshParameters::AuthenticationTypeSpecificKey);
});
timeout.setDefaultValue(10);
timeout.setLabelText(Tr::tr("Timeout:"));
timeout.setToolTip(Tr::tr("The device's SSH connection timeout"));
}
} // namespace ProjectExplorer

View File

@@ -73,6 +73,25 @@ private:
QString m_userName;
};
class PROJECTEXPLORER_EXPORT SshParametersAspectContainer : public Utils::AspectContainer
{
public:
SshParametersAspectContainer();
SshParameters sshParameters() const;
void setSshParameters(const SshParameters &params);
public:
Utils::FilePathAspect privateKeyFile{this};
Utils::IntegerAspect timeout{this};
Utils::TypedSelectionAspect<SshParameters::AuthenticationType> authenticationType{this};
Utils::TypedSelectionAspect<SshHostKeyCheckingMode> hostKeyCheckingMode{this};
Utils::StringAspect host{this};
Utils::IntegerAspect port{this};
Utils::StringAspect userName{this};
};
#ifdef WITH_TESTS
namespace SshTest {
const QString PROJECTEXPLORER_EXPORT getHostFromEnvironment();

View File

@@ -231,7 +231,6 @@ const char USER_ENVIRONMENT_CHANGES_KEY[] = "ProjectExplorer.BuildConfiguration.
// Called "RemoteLinux." for backwards compatibility
const char SUPPORTS_RSYNC[] = "RemoteLinux.SupportsRSync";
const char SUPPORTS_SFTP[] = "RemoteLinux.SupportsSftp";
const char SSH_FORWARD_DEBUGSERVER_PORT[] = "RemoteLinux.SshForwardDebugServerPort";
// SDKs related ids:
const char SDK_SETTINGS_CATEGORY[] = "AN.SDKs";

View File

@@ -634,7 +634,7 @@ void RunControlPrivate::startPortsGathererIfNeededAndContinueStart()
QUrl RunControlPrivate::getNextChannel(PortList *portList, const QList<Port> &usedPorts)
{
QUrl result;
if (q->device()->extraData(Constants::SSH_FORWARD_DEBUGSERVER_PORT).toBool()) {
if (q->device()->sshForwardDebugServerPort()) {
result.setScheme(urlTcpScheme());
result.setHost("localhost");
} else {

View File

@@ -66,9 +66,9 @@ public:
setMachineType(IDevice::Hardware);
SshParameters sshParams;
sshParams.setTimeout(10);
setSshParameters(sshParams);
setDefaultSshParameters(sshParams);
setFreePorts(PortList::fromString("10000-10100"));
setExtraData(RemoteLinux::Constants::SourceProfile, true);
sourceProfile.setDefaultValue(true);
addDeviceAction({Tr::tr("Deploy Qt libraries..."), [](const IDevice::Ptr &device) {
QnxDeployQtLibrariesDialog dialog(device, Core::ICore::dialogParent());

View File

@@ -47,7 +47,7 @@ TestLinuxDeviceFactory::TestLinuxDeviceFactory()
device->setupId(IDevice::ManuallyAdded);
device->setType("test");
qDebug() << "device : " << device->type();
device->setSshParameters(SshTest::getParameters());
device->sshParametersAspectContainer().setSshParameters(SshTest::getParameters());
return device;
});
}

View File

@@ -3,6 +3,7 @@
#include "genericlinuxdeviceconfigurationwidget.h"
#include "linuxdevice.h"
#include "remotelinux_constants.h"
#include "remotelinuxtr.h"
#include "sshkeycreationdialog.h"
@@ -33,325 +34,62 @@ using namespace Utils;
namespace RemoteLinux::Internal {
GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
const IDevice::Ptr &device) :
IDeviceWidget(device)
const IDevice::Ptr &device)
: IDeviceWidget(device)
{
m_defaultAuthButton = new QRadioButton(Tr::tr("Default"), this);
m_keyButton = new QRadioButton(Tr::tr("Specific &key"));
m_hostLineEdit = new FancyLineEdit(this);
m_hostLineEdit->setPlaceholderText(Tr::tr("IP or host name of the device"));
m_hostLineEdit->setHistoryCompleter("HostName");
m_sshPortSpinBox = new QSpinBox(this);
m_sshPortSpinBox->setMinimum(0);
m_sshPortSpinBox->setMaximum(65535);
m_sshPortSpinBox->setValue(22);
m_hostKeyCheckBox = new QCheckBox(Tr::tr("&Check host key"));
m_portsLineEdit = new FancyLineEdit(this);
m_portsLineEdit->setToolTip(Tr::tr("You can enter lists and ranges like this: '1024,1026-1028,1030'."));
m_portsLineEdit->setHistoryCompleter("PortRange");
m_portsWarningLabel = new QLabel(this);
m_timeoutSpinBox = new QSpinBox(this);
m_timeoutSpinBox->setMaximum(10000);
m_timeoutSpinBox->setSingleStep(10);
m_timeoutSpinBox->setValue(1000);
m_timeoutSpinBox->setSuffix(Tr::tr("s"));
m_userLineEdit = new FancyLineEdit(this);
m_userLineEdit->setHistoryCompleter("UserName");
m_keyLabel = new QLabel(Tr::tr("Private key file:"));
m_keyFileLineEdit = new PathChooser(this);
auto createKeyButton = new QPushButton(Tr::tr("Create New..."));
m_machineTypeValueLabel = new QLabel(this);
const QString hint = Tr::tr("Leave empty to look up executable in $PATH");
m_gdbServerLineEdit = new PathChooser(this);
m_gdbServerLineEdit->setExpectedKind(PathChooser::ExistingCommand);
m_gdbServerLineEdit->setPlaceholderText(hint);
m_gdbServerLineEdit->setToolTip(hint);
m_gdbServerLineEdit->setHistoryCompleter("GdbServer");
m_gdbServerLineEdit->setAllowPathFromDevice(true);
m_qmlRuntimeLineEdit = new PathChooser(this);
m_qmlRuntimeLineEdit->setExpectedKind(PathChooser::ExistingCommand);
m_qmlRuntimeLineEdit->setPlaceholderText(hint);
m_qmlRuntimeLineEdit->setToolTip(hint);
m_qmlRuntimeLineEdit->setHistoryCompleter("QmlRuntime");
m_qmlRuntimeLineEdit->setAllowPathFromDevice(true);
m_sourceProfileCheckBox =
new QCheckBox(Tr::tr("Source %1 and %2").arg("/etc/profile").arg("$HOME/.profile"));
m_linkDeviceComboBox = new QComboBox;
m_linkDeviceComboBox->addItem(Tr::tr("Direct"), QVariant());
auto dm = DeviceManager::instance();
const int dmCount = dm->deviceCount();
for (int i = 0; i < dmCount; ++i) {
IDevice::ConstPtr dev = dm->deviceAt(i);
if (dev->id() != device->id())
m_linkDeviceComboBox->addItem(dev->displayName(), dev->id().toSetting());
}
auto sshPortLabel = new QLabel(Tr::tr("&SSH port:"));
sshPortLabel->setBuddy(m_sshPortSpinBox);
m_useSshPortForwardingForDebugging = new QCheckBox;
m_useSshPortForwardingForDebugging->setText(Tr::tr("Use SSH port forwarding for debugging"));
m_useSshPortForwardingForDebugging->setToolTip(
Tr::tr("Enable debugging on remote targets which cannot expose gdbserver ports.\n"
"The ssh tunneling is used to map the remote gdbserver port to localhost.\n"
"The local and remote ports are determined automatically."));
const QString machineType = device->machineType() == IDevice::Hardware
? Tr::tr("Physical Device")
: Tr::tr("Emulator");
auto linuxDevice = std::dynamic_pointer_cast<LinuxDevice>(device);
QTC_ASSERT(linuxDevice, return);
using namespace Layouting;
auto portWarningLabel = new QLabel(
QString("<font color=\"red\">%1</font>").arg(Tr::tr("You will need at least one port.")));
auto updatePortWarningLabel = [portWarningLabel, device]() {
portWarningLabel->setVisible(device->freePortsAspect.volatileValue().isEmpty());
};
updatePortWarningLabel();
// clang-format off
connect(&device->freePortsAspect, &PortListAspect::volatileValueChanged, this, updatePortWarningLabel);
Form {
Tr::tr("Machine type:"), m_machineTypeValueLabel, st, br,
Tr::tr("Authentication type:"), m_defaultAuthButton, m_keyButton, st, br,
Tr::tr("&Host name:"), m_hostLineEdit, sshPortLabel, m_sshPortSpinBox, m_hostKeyCheckBox, st, br,
Tr::tr("Free ports:"), m_portsLineEdit, m_portsWarningLabel, Tr::tr("Timeout:"), m_timeoutSpinBox, st, br,
Tr::tr("&Username:"), m_userLineEdit, st, br,
m_keyLabel, m_keyFileLineEdit, createKeyButton, br,
Tr::tr("GDB server executable:"), m_gdbServerLineEdit, br,
Tr::tr("QML runtime executable:"), m_qmlRuntimeLineEdit, br,
QString(), m_sourceProfileCheckBox, br,
QString(), m_useSshPortForwardingForDebugging, br,
Tr::tr("Access via:"), m_linkDeviceComboBox, br,
Tr::tr("Machine type:"), machineType, st, br,
device->sshParametersAspectContainer().authenticationType.labelText(), device->sshParametersAspectContainer().authenticationType, st, br,
device->sshParametersAspectContainer().host, device->sshParametersAspectContainer().port, device->sshParametersAspectContainer().hostKeyCheckingMode, st, br,
device->freePortsAspect, portWarningLabel, device->sshParametersAspectContainer().timeout, st, br,
device->sshParametersAspectContainer().userName, st, br,
device->sshParametersAspectContainer().privateKeyFile, createKeyButton, br,
device->debugServerPathAspect, br,
device->qmlRunCommandAspect, br,
linuxDevice->sourceProfile, br,
device->sshForwardDebugServerPort, br,
device->linkDevice, br,
}.attachTo(this);
// clang-format on
connect(m_hostLineEdit, &QLineEdit::editingFinished,
this, &GenericLinuxDeviceConfigurationWidget::hostNameEditingFinished);
connect(m_userLineEdit, &QLineEdit::editingFinished,
this, &GenericLinuxDeviceConfigurationWidget::userNameEditingFinished);
connect(m_keyFileLineEdit, &PathChooser::editingFinished,
this, &GenericLinuxDeviceConfigurationWidget::keyFileEditingFinished);
connect(m_keyFileLineEdit, &PathChooser::browsingFinished,
this, &GenericLinuxDeviceConfigurationWidget::keyFileEditingFinished);
connect(m_keyButton, &QAbstractButton::toggled,
this, &GenericLinuxDeviceConfigurationWidget::authenticationTypeChanged);
connect(m_timeoutSpinBox, &QAbstractSpinBox::editingFinished,
this, &GenericLinuxDeviceConfigurationWidget::timeoutEditingFinished);
connect(m_timeoutSpinBox, &QSpinBox::valueChanged,
this, &GenericLinuxDeviceConfigurationWidget::timeoutEditingFinished);
connect(m_sshPortSpinBox, &QAbstractSpinBox::editingFinished,
this, &GenericLinuxDeviceConfigurationWidget::sshPortEditingFinished);
connect(m_sshPortSpinBox, &QSpinBox::valueChanged,
this, &GenericLinuxDeviceConfigurationWidget::sshPortEditingFinished);
connect(m_portsLineEdit, &QLineEdit::editingFinished,
this, &GenericLinuxDeviceConfigurationWidget::handleFreePortsChanged);
connect(createKeyButton, &QAbstractButton::clicked,
this, &GenericLinuxDeviceConfigurationWidget::createNewKey);
connect(m_gdbServerLineEdit, &PathChooser::editingFinished,
this, &GenericLinuxDeviceConfigurationWidget::gdbServerEditingFinished);
connect(m_qmlRuntimeLineEdit, &PathChooser::editingFinished,
this, &GenericLinuxDeviceConfigurationWidget::qmlRuntimeEditingFinished);
connect(m_hostKeyCheckBox, &QCheckBox::toggled,
this, &GenericLinuxDeviceConfigurationWidget::hostKeyCheckingChanged);
connect(m_sourceProfileCheckBox, &QCheckBox::toggled,
this, &GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged);
connect(m_linkDeviceComboBox, &QComboBox::currentIndexChanged,
this, &GenericLinuxDeviceConfigurationWidget::linkDeviceChanged);
connect(m_useSshPortForwardingForDebugging, &QCheckBox::toggled,
this, &GenericLinuxDeviceConfigurationWidget::sshPortForwardingForDebugging);
initGui();
connect(
createKeyButton,
&QAbstractButton::clicked,
this,
&GenericLinuxDeviceConfigurationWidget::createNewKey);
}
GenericLinuxDeviceConfigurationWidget::~GenericLinuxDeviceConfigurationWidget() = default;
void GenericLinuxDeviceConfigurationWidget::authenticationTypeChanged()
{
SshParameters sshParams = device()->sshParameters();
const bool useKeyFile = m_keyButton->isChecked();
sshParams.setAuthenticationType(
useKeyFile ? SshParameters::AuthenticationTypeSpecificKey
: SshParameters::AuthenticationTypeAll);
device()->setSshParameters(sshParams);
m_keyFileLineEdit->setEnabled(useKeyFile);
m_keyLabel->setEnabled(useKeyFile);
}
void GenericLinuxDeviceConfigurationWidget::hostNameEditingFinished()
{
SshParameters sshParams = device()->sshParameters();
sshParams.setHost(m_hostLineEdit->text().trimmed());
device()->setSshParameters(sshParams);
}
void GenericLinuxDeviceConfigurationWidget::sshPortEditingFinished()
{
SshParameters sshParams = device()->sshParameters();
sshParams.setPort(m_sshPortSpinBox->value());
device()->setSshParameters(sshParams);
}
void GenericLinuxDeviceConfigurationWidget::timeoutEditingFinished()
{
SshParameters sshParams = device()->sshParameters();
sshParams.setTimeout(m_timeoutSpinBox->value());
device()->setSshParameters(sshParams);
}
void GenericLinuxDeviceConfigurationWidget::userNameEditingFinished()
{
SshParameters sshParams = device()->sshParameters();
sshParams.setUserName(m_userLineEdit->text());
device()->setSshParameters(sshParams);
}
void GenericLinuxDeviceConfigurationWidget::keyFileEditingFinished()
{
SshParameters sshParams = device()->sshParameters();
sshParams.setPrivateKeyFile(m_keyFileLineEdit->filePath());
device()->setSshParameters(sshParams);
}
void GenericLinuxDeviceConfigurationWidget::gdbServerEditingFinished()
{
device()->setDebugServerPath(m_gdbServerLineEdit->filePath());
}
void GenericLinuxDeviceConfigurationWidget::qmlRuntimeEditingFinished()
{
device()->setQmlRunCommand(m_qmlRuntimeLineEdit->filePath());
}
void GenericLinuxDeviceConfigurationWidget::handleFreePortsChanged()
{
device()->setFreePorts(PortList::fromString(m_portsLineEdit->text()));
updatePortsWarningLabel();
}
void GenericLinuxDeviceConfigurationWidget::setPrivateKey(const FilePath &path)
{
m_keyFileLineEdit->setFilePath(path);
keyFileEditingFinished();
}
void GenericLinuxDeviceConfigurationWidget::createNewKey()
{
SshKeyCreationDialog dialog(this);
if (dialog.exec() == QDialog::Accepted)
setPrivateKey(dialog.privateKeyFilePath());
}
void GenericLinuxDeviceConfigurationWidget::hostKeyCheckingChanged(bool doCheck)
{
SshParameters sshParams = device()->sshParameters();
sshParams.setHostKeyCheckingMode(
doCheck ? SshHostKeyCheckingAllowNoMatch : SshHostKeyCheckingNone);
device()->setSshParameters(sshParams);
}
void GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged(bool doCheck)
{
device()->setExtraData(Constants::SourceProfile, doCheck);
}
void GenericLinuxDeviceConfigurationWidget::linkDeviceChanged(int index)
{
const QVariant deviceId = m_linkDeviceComboBox->itemData(index);
device()->setExtraData(Constants::LinkDevice, deviceId);
}
void GenericLinuxDeviceConfigurationWidget::sshPortForwardingForDebugging(bool on)
{
device()->setExtraData(ProjectExplorer::Constants::SSH_FORWARD_DEBUGSERVER_PORT, on);
}
void GenericLinuxDeviceConfigurationWidget::updateDeviceFromUi()
{
hostNameEditingFinished();
sshPortEditingFinished();
timeoutEditingFinished();
userNameEditingFinished();
keyFileEditingFinished();
handleFreePortsChanged();
gdbServerEditingFinished();
sshPortEditingFinished();
timeoutEditingFinished();
sourceProfileCheckingChanged(m_sourceProfileCheckBox->isChecked());
linkDeviceChanged(m_linkDeviceComboBox->currentIndex());
sshPortForwardingForDebugging(m_useSshPortForwardingForDebugging->isChecked());
qmlRuntimeEditingFinished();
}
void GenericLinuxDeviceConfigurationWidget::updatePortsWarningLabel()
{
m_portsWarningLabel->setVisible(!device()->freePorts().hasMore());
}
void GenericLinuxDeviceConfigurationWidget::initGui()
{
if (device()->machineType() == IDevice::Hardware)
m_machineTypeValueLabel->setText(Tr::tr("Physical Device"));
else
m_machineTypeValueLabel->setText(Tr::tr("Emulator"));
m_portsWarningLabel->setPixmap(Utils::Icons::CRITICAL.pixmap());
m_portsWarningLabel->setToolTip(QLatin1String("<font color=\"red\">")
+ Tr::tr("You will need at least one port.") + QLatin1String("</font>"));
m_keyFileLineEdit->setExpectedKind(PathChooser::File);
m_keyFileLineEdit->setHistoryCompleter("Ssh.KeyFile.History");
m_keyFileLineEdit->lineEdit()->setMinimumWidth(0);
QRegularExpressionValidator * const portsValidator
= new QRegularExpressionValidator(QRegularExpression(PortList::regularExpression()), this);
m_portsLineEdit->setValidator(portsValidator);
const SshParameters &sshParams = device()->sshParameters();
switch (sshParams.authenticationType()) {
case SshParameters::AuthenticationTypeSpecificKey:
m_keyButton->setChecked(true);
break;
case SshParameters::AuthenticationTypeAll:
m_defaultAuthButton->setChecked(true);
break;
if (dialog.exec() == QDialog::Accepted) {
device()->sshParametersAspectContainer().privateKeyFile.setValue(
dialog.privateKeyFilePath());
}
m_timeoutSpinBox->setValue(sshParams.timeout());
m_hostLineEdit->setEnabled(!device()->isAutoDetected());
m_sshPortSpinBox->setEnabled(!device()->isAutoDetected());
m_hostKeyCheckBox->setChecked(sshParams.hostKeyCheckingMode() != SshHostKeyCheckingNone);
m_sourceProfileCheckBox->setChecked(device()->extraData(Constants::SourceProfile).toBool());
Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice));
auto dm = DeviceManager::instance();
int found = -1;
int minus = 0;
for (int i = 0, n = dm->deviceCount(); i < n; ++i) {
const auto otherId = dm->deviceAt(i)->id();
if (otherId == linkDeviceId) {
found = i;
break;
} else if (otherId == device()->id()) {
// Since we ourselves do not appear in the combo box, we need to adjust the index.
minus = 1;
}
}
m_linkDeviceComboBox->setCurrentIndex(found + 1 - minus); // There's the "Direct" entry first.
m_hostLineEdit->setText(sshParams.host());
m_sshPortSpinBox->setValue(sshParams.port());
m_portsLineEdit->setText(device()->freePorts().toString());
m_timeoutSpinBox->setValue(sshParams.timeout());
m_userLineEdit->setText(sshParams.userName());
m_keyFileLineEdit->setFilePath(sshParams.privateKeyFile());
m_keyFileLineEdit->setEnabled(
sshParams.authenticationType() == SshParameters::AuthenticationTypeSpecificKey);
m_gdbServerLineEdit->setFilePath(device()->debugServerPath());
m_qmlRuntimeLineEdit->setFilePath(device()->qmlRunCommand());
m_useSshPortForwardingForDebugging->setChecked(
device()->extraData(ProjectExplorer::Constants::SSH_FORWARD_DEBUGSERVER_PORT).toBool());
updatePortsWarningLabel();
}
} // RemoteLinux::Internal

View File

@@ -31,43 +31,8 @@ public:
~GenericLinuxDeviceConfigurationWidget() override;
private:
void authenticationTypeChanged();
void hostNameEditingFinished();
void sshPortEditingFinished();
void timeoutEditingFinished();
void userNameEditingFinished();
void keyFileEditingFinished();
void gdbServerEditingFinished();
void qmlRuntimeEditingFinished();
void handleFreePortsChanged();
void setPrivateKey(const Utils::FilePath &path);
void createNewKey();
void hostKeyCheckingChanged(bool doCheck);
void sourceProfileCheckingChanged(bool doCheck);
void linkDeviceChanged(int index);
void sshPortForwardingForDebugging(bool on);
void updateDeviceFromUi() override;
void updatePortsWarningLabel();
void initGui();
QRadioButton *m_defaultAuthButton;
QLabel *m_keyLabel;
QRadioButton *m_keyButton;
Utils::FancyLineEdit *m_hostLineEdit;
QSpinBox *m_sshPortSpinBox;
QCheckBox *m_hostKeyCheckBox;
Utils::FancyLineEdit *m_portsLineEdit;
QLabel *m_portsWarningLabel;
Utils::FancyLineEdit *m_userLineEdit;
QSpinBox *m_timeoutSpinBox;
Utils::PathChooser *m_keyFileLineEdit;
QLabel *m_machineTypeValueLabel;
Utils::PathChooser *m_gdbServerLineEdit;
Utils::PathChooser *m_qmlRuntimeLineEdit;
QCheckBox *m_sourceProfileCheckBox;
QComboBox *m_linkDeviceComboBox;
QCheckBox *m_useSshPortForwardingForDebugging;
void updateDeviceFromUi() override {}
};
} // RemoteLinux::Internal

View File

@@ -656,7 +656,7 @@ void SshProcessInterfacePrivate::start()
{
m_sshParameters = m_device->sshParameters();
const Id linkDeviceId = Id::fromSetting(m_device->extraData(Constants::LinkDevice));
const Id linkDeviceId = Id::fromSetting(m_device->linkDevice.value());
if (const IDevice::ConstPtr linkDevice = DeviceManager::instance()->find(linkDeviceId)) {
CommandLine cmd{linkDevice->filePath("ssh")};
if (!m_sshParameters.userName().isEmpty()) {
@@ -775,10 +775,13 @@ void SshProcessInterfacePrivate::doStart()
CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const
{
auto linuxDevice = std::dynamic_pointer_cast<const LinuxDevice>(m_device);
QTC_ASSERT(linuxDevice, return {});
const FilePath sshBinary = SshSettings::sshFilePath();
const bool useTerminal = q->m_setup.m_terminalMode != TerminalMode::Off || q->m_setup.m_ptyData;
const bool usePidMarker = !useTerminal;
const bool sourceProfile = m_device->extraData(Constants::SourceProfile).toBool();
const bool sourceProfile = linuxDevice->sourceProfile();
const bool useX = !m_sshParameters.x11DisplayName().isEmpty();
CommandLine cmd{sshBinary};
@@ -1029,7 +1032,13 @@ LinuxDevice::LinuxDevice()
setFreePorts(PortList::fromString(QLatin1String("10000-10100")));
SshParameters sshParams;
sshParams.setTimeout(10);
setSshParameters(sshParams);
setDefaultSshParameters(sshParams);
sourceProfile.setSettingsKey("SourceProfile");
sourceProfile.setDefaultValue(true);
sourceProfile.setToolTip(Tr::tr("Source profile before executing commands"));
sourceProfile.setLabelText(Tr::tr("Source %1 and %2").arg("/etc/profile").arg("$HOME/.profile"));
sourceProfile.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox);
addDeviceAction({Tr::tr("Deploy Public Key..."), [](const IDevice::Ptr &device) {
if (auto d = Internal::PublicKeyDeploymentDialog::createDialog(device)) {
@@ -1368,6 +1377,35 @@ void Internal::LinuxDeviceFactory::shutdownExistingDevices()
}
});
}
namespace {
static const char SourceProfile[] = "RemoteLinux.SourceProfile";
static void backwardsFromExtraData(LinuxDevice *device)
{
QVariant sourceProfile = device->extraData(SourceProfile);
if (sourceProfile.isValid())
device->sourceProfile.setValue(sourceProfile.toBool());
}
static void backwardsToExtraData(LinuxDevice *device)
{
device->setExtraData(SourceProfile, device->sourceProfile.value());
}
} // namespace
void LinuxDevice::fromMap(const Utils::Store &map)
{
ProjectExplorer::IDevice::fromMap(map);
backwardsFromExtraData(this);
}
void LinuxDevice::toMap(Utils::Store &map) const
{
backwardsToExtraData(const_cast<LinuxDevice *>(this));
ProjectExplorer::IDevice::toMap(map);
}
} // namespace RemoteLinux
#include "linuxdevice.moc"

View File

@@ -76,6 +76,12 @@ public:
void shutdown();
void fromMap(const Utils::Store &map) override;
void toMap(Utils::Store &map) const override;
public:
Utils::BoolAspect sourceProfile{this};
protected:
LinuxDevice();

View File

@@ -19,8 +19,6 @@ const char GenericDeployStepId[] = "RemoteLinux.RsyncDeployStep";
const char CustomCommandDeployStepId[] = "RemoteLinux.GenericRemoteLinuxCustomCommandDeploymentStep";
const char KillAppStepId[] = "RemoteLinux.KillAppStep";
const char SourceProfile[] = "RemoteLinux.SourceProfile";
const char LinkDevice[] = "RemoteLinux.LinkDevice";
const char SshForwardPort[] = "RemoteLinux.SshForwardPort";
const char DisableSharing[] = "RemoteLinux.DisableSharing";

View File

@@ -121,7 +121,7 @@ private:
void start() final
{
m_sshParameters = displayless(m_device.sshParameters());
const Id linkDeviceId = Id::fromSetting(m_device.extraData(Constants::LinkDevice));
const Id linkDeviceId = m_device.linkDeviceId();
const auto linkDevice = DeviceManager::instance()->find(linkDeviceId);
const bool useConnectionSharing = !linkDevice && SshSettings::connectionSharingEnabled();
@@ -186,7 +186,7 @@ private:
FilePath sftpBinary = SshSettings::sftpFilePath();
// This is a hack. We only test the last hop here.
const Id linkDeviceId = Id::fromSetting(device().extraData(Constants::LinkDevice));
const Id linkDeviceId = device().linkDeviceId();
if (const auto linkDevice = DeviceManager::instance()->find(linkDeviceId))
sftpBinary = linkDevice->filePath(sftpBinary.fileName()).searchInPath();