forked from qt-creator/qt-creator
Terminal: Add remote devices to shell selection
Change-Id: Id28471aaf3e91ef493f48ab28207230f3fb513c2 Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
@@ -18,10 +18,12 @@ struct HooksPrivate
|
|||||||
})
|
})
|
||||||
, m_createTerminalProcessInterfaceHook(
|
, m_createTerminalProcessInterfaceHook(
|
||||||
[]() -> ProcessInterface * { return new Internal::TerminalImpl(); })
|
[]() -> ProcessInterface * { return new Internal::TerminalImpl(); })
|
||||||
|
, m_getTerminalCommandsForDevicesHook([]() -> QList<NameAndCommandLine> { return {}; })
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Hooks::OpenTerminalHook m_openTerminalHook;
|
Hooks::OpenTerminalHook m_openTerminalHook;
|
||||||
Hooks::CreateTerminalProcessInterfaceHook m_createTerminalProcessInterfaceHook;
|
Hooks::CreateTerminalProcessInterfaceHook m_createTerminalProcessInterfaceHook;
|
||||||
|
Hooks::GetTerminalCommandsForDevicesHook m_getTerminalCommandsForDevicesHook;
|
||||||
};
|
};
|
||||||
|
|
||||||
Hooks &Hooks::instance()
|
Hooks &Hooks::instance()
|
||||||
@@ -40,9 +42,15 @@ Hooks::OpenTerminalHook &Hooks::openTerminalHook()
|
|||||||
{
|
{
|
||||||
return d->m_openTerminalHook;
|
return d->m_openTerminalHook;
|
||||||
}
|
}
|
||||||
|
|
||||||
Hooks::CreateTerminalProcessInterfaceHook &Hooks::createTerminalProcessInterfaceHook()
|
Hooks::CreateTerminalProcessInterfaceHook &Hooks::createTerminalProcessInterfaceHook()
|
||||||
{
|
{
|
||||||
return d->m_createTerminalProcessInterfaceHook;
|
return d->m_createTerminalProcessInterfaceHook;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Hooks::GetTerminalCommandsForDevicesHook &Hooks::getTerminalCommandsForDevicesHook()
|
||||||
|
{
|
||||||
|
return d->m_getTerminalCommandsForDevicesHook;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Utils::Terminal
|
} // namespace Utils::Terminal
|
||||||
|
|||||||
@@ -48,19 +48,27 @@ struct OpenTerminalParameters
|
|||||||
ExitBehavior m_exitBehavior{ExitBehavior::Close};
|
ExitBehavior m_exitBehavior{ExitBehavior::Close};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct NameAndCommandLine
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
CommandLine commandLine;
|
||||||
|
};
|
||||||
|
|
||||||
class QTCREATOR_UTILS_EXPORT Hooks
|
class QTCREATOR_UTILS_EXPORT Hooks
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using OpenTerminalHook = Hook<void, const OpenTerminalParameters &>;
|
using OpenTerminalHook = Hook<void, const OpenTerminalParameters &>;
|
||||||
using CreateTerminalProcessInterfaceHook = Hook<ProcessInterface *>;
|
using CreateTerminalProcessInterfaceHook = Hook<ProcessInterface *>;
|
||||||
|
using GetTerminalCommandsForDevicesHook = Hook<QList<NameAndCommandLine>>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Hooks &instance();
|
static Hooks &instance();
|
||||||
|
~Hooks();
|
||||||
|
|
||||||
OpenTerminalHook &openTerminalHook();
|
OpenTerminalHook &openTerminalHook();
|
||||||
CreateTerminalProcessInterfaceHook &createTerminalProcessInterfaceHook();
|
CreateTerminalProcessInterfaceHook &createTerminalProcessInterfaceHook();
|
||||||
|
GetTerminalCommandsForDevicesHook &getTerminalCommandsForDevicesHook();
|
||||||
|
|
||||||
~Hooks();
|
|
||||||
private:
|
private:
|
||||||
Hooks();
|
Hooks();
|
||||||
std::unique_ptr<HooksPrivate> d;
|
std::unique_ptr<HooksPrivate> d;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
#include <utils/stringutils.h>
|
#include <utils/stringutils.h>
|
||||||
|
#include <utils/terminalhooks.h>
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
@@ -457,6 +458,19 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique<DeviceManager
|
|||||||
};
|
};
|
||||||
|
|
||||||
QtcProcess::setRemoteProcessHooks(processHooks);
|
QtcProcess::setRemoteProcessHooks(processHooks);
|
||||||
|
|
||||||
|
Terminal::Hooks::instance().getTerminalCommandsForDevicesHook().set(
|
||||||
|
[this]() -> QList<Terminal::NameAndCommandLine> {
|
||||||
|
QList<Terminal::NameAndCommandLine> result;
|
||||||
|
for (const IDevice::ConstPtr device : d->devices) {
|
||||||
|
if (device->type() == Constants::DESKTOP_DEVICE_TYPE)
|
||||||
|
continue;
|
||||||
|
const std::optional<CommandLine> terminalCommand = device->terminalCommand({}, {});
|
||||||
|
if (terminalCommand)
|
||||||
|
result << Terminal::NameAndCommandLine{device->displayName(), *terminalCommand};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceManager::~DeviceManager()
|
DeviceManager::~DeviceManager()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
add_qtc_plugin(Terminal
|
add_qtc_plugin(Terminal
|
||||||
SKIP_TRANSLATION
|
|
||||||
PLUGIN_DEPENDS Core
|
PLUGIN_DEPENDS Core
|
||||||
DEPENDS libvterm ptyqt
|
DEPENDS libvterm ptyqt
|
||||||
SOURCES
|
SOURCES
|
||||||
@@ -13,5 +12,6 @@ add_qtc_plugin(Terminal
|
|||||||
terminalsettings.cpp terminalsettings.h
|
terminalsettings.cpp terminalsettings.h
|
||||||
terminalsettingspage.cpp terminalsettingspage.h
|
terminalsettingspage.cpp terminalsettingspage.h
|
||||||
scrollback.h scrollback.cpp
|
scrollback.h scrollback.cpp
|
||||||
|
shellmodel.cpp shellmodel.h
|
||||||
keys.cpp keys.h
|
keys.cpp keys.h
|
||||||
)
|
)
|
||||||
|
|||||||
111
src/plugins/terminal/shellmodel.cpp
Normal file
111
src/plugins/terminal/shellmodel.cpp
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include "shellmodel.h"
|
||||||
|
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/environment.h>
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
|
||||||
|
#include <QFileIconProvider>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
namespace Terminal::Internal {
|
||||||
|
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
|
FilePaths availableShells()
|
||||||
|
{
|
||||||
|
if (Utils::HostOsInfo::isWindowsHost()) {
|
||||||
|
FilePaths shells;
|
||||||
|
|
||||||
|
FilePath comspec = FilePath::fromUserInput(qtcEnvironmentVariable("COMSPEC"));
|
||||||
|
shells << comspec;
|
||||||
|
|
||||||
|
if (comspec.fileName() != "cmd.exe") {
|
||||||
|
FilePath cmd = FilePath::fromUserInput(QStandardPaths::findExecutable("cmd.exe"));
|
||||||
|
shells << cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
FilePath powershell = FilePath::fromUserInput(
|
||||||
|
QStandardPaths::findExecutable("powershell.exe"));
|
||||||
|
if (powershell.exists())
|
||||||
|
shells << powershell;
|
||||||
|
|
||||||
|
FilePath bash = FilePath::fromUserInput(QStandardPaths::findExecutable("bash.exe"));
|
||||||
|
if (bash.exists())
|
||||||
|
shells << bash;
|
||||||
|
|
||||||
|
FilePath git_bash = FilePath::fromUserInput(QStandardPaths::findExecutable("git.exe"));
|
||||||
|
if (git_bash.exists())
|
||||||
|
shells << git_bash.parentDir().parentDir().pathAppended("usr/bin/bash.exe");
|
||||||
|
|
||||||
|
FilePath msys2_bash = FilePath::fromUserInput(QStandardPaths::findExecutable("msys2.exe"));
|
||||||
|
if (msys2_bash.exists())
|
||||||
|
shells << msys2_bash.parentDir().pathAppended("usr/bin/bash.exe");
|
||||||
|
|
||||||
|
return shells;
|
||||||
|
} else {
|
||||||
|
FilePath shellsFile = FilePath::fromString("/etc/shells");
|
||||||
|
const auto shellFileContent = shellsFile.fileContents();
|
||||||
|
QTC_ASSERT_EXPECTED(shellFileContent, return {});
|
||||||
|
|
||||||
|
QString shellFileContentString = QString::fromUtf8(*shellFileContent);
|
||||||
|
|
||||||
|
// Filter out comments ...
|
||||||
|
const QStringList lines
|
||||||
|
= Utils::filtered(shellFileContentString.split('\n', Qt::SkipEmptyParts),
|
||||||
|
[](const QString &line) { return !line.trimmed().startsWith('#'); });
|
||||||
|
|
||||||
|
// Convert lines to file paths ...
|
||||||
|
const FilePaths shells = Utils::transform(lines, [](const QString &line) {
|
||||||
|
return FilePath::fromUserInput(line.trimmed());
|
||||||
|
});
|
||||||
|
|
||||||
|
// ... and filter out non-existing shells.
|
||||||
|
return Utils::filtered(shells, [](const FilePath &shell) { return shell.exists(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShellModelPrivate
|
||||||
|
{
|
||||||
|
QList<ShellModelItem> localShells;
|
||||||
|
};
|
||||||
|
|
||||||
|
ShellModel::ShellModel(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, d(new ShellModelPrivate())
|
||||||
|
{
|
||||||
|
QFileIconProvider iconProvider;
|
||||||
|
|
||||||
|
const FilePaths shells = availableShells();
|
||||||
|
for (const FilePath &shell : shells) {
|
||||||
|
ShellModelItem item;
|
||||||
|
item.icon = iconProvider.icon(shell.toFileInfo());
|
||||||
|
item.name = shell.toUserOutput();
|
||||||
|
item.openParameters.shellCommand = {shell, {}};
|
||||||
|
d->localShells << item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellModel::~ShellModel() = default;
|
||||||
|
|
||||||
|
QList<ShellModelItem> ShellModel::local() const
|
||||||
|
{
|
||||||
|
return d->localShells;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ShellModelItem> ShellModel::remote() const
|
||||||
|
{
|
||||||
|
const auto deviceCmds = Utils::Terminal::Hooks::instance().getTerminalCommandsForDevicesHook()();
|
||||||
|
|
||||||
|
const QList<ShellModelItem> deviceItems
|
||||||
|
= Utils::transform(deviceCmds,
|
||||||
|
[](const Utils::Terminal::NameAndCommandLine &item) -> ShellModelItem {
|
||||||
|
return ShellModelItem{item.name, {}, {item.commandLine, {}, {}}};
|
||||||
|
});
|
||||||
|
|
||||||
|
return deviceItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Terminal::Internal
|
||||||
37
src/plugins/terminal/shellmodel.h
Normal file
37
src/plugins/terminal/shellmodel.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utils/terminalhooks.h>
|
||||||
|
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Terminal::Internal {
|
||||||
|
struct ShellModelPrivate;
|
||||||
|
|
||||||
|
struct ShellModelItem
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QIcon icon;
|
||||||
|
Utils::Terminal::OpenTerminalParameters openParameters;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ShellModel : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ShellModel(QObject *parent = nullptr);
|
||||||
|
~ShellModel();
|
||||||
|
|
||||||
|
QList<ShellModelItem> local() const;
|
||||||
|
QList<ShellModelItem> remote() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<ShellModelPrivate> d;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Terminal::Internal
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "terminalpane.h"
|
#include "terminalpane.h"
|
||||||
|
|
||||||
|
#include "shellmodel.h"
|
||||||
#include "terminaltr.h"
|
#include "terminaltr.h"
|
||||||
#include "terminalwidget.h"
|
#include "terminalwidget.h"
|
||||||
|
|
||||||
@@ -13,7 +14,6 @@
|
|||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
#include <utils/utilsicons.h>
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
#include <QFileIconProvider>
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
@@ -21,59 +21,6 @@ namespace Terminal {
|
|||||||
|
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
FilePaths availableShells()
|
|
||||||
{
|
|
||||||
if (Utils::HostOsInfo::isWindowsHost()) {
|
|
||||||
FilePaths shells;
|
|
||||||
|
|
||||||
FilePath comspec = FilePath::fromUserInput(qtcEnvironmentVariable("COMSPEC"));
|
|
||||||
shells << comspec;
|
|
||||||
|
|
||||||
if (comspec.fileName() != "cmd.exe") {
|
|
||||||
FilePath cmd = FilePath::fromUserInput(QStandardPaths::findExecutable("cmd.exe"));
|
|
||||||
shells << cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
FilePath powershell = FilePath::fromUserInput(
|
|
||||||
QStandardPaths::findExecutable("powershell.exe"));
|
|
||||||
if (powershell.exists())
|
|
||||||
shells << powershell;
|
|
||||||
|
|
||||||
FilePath bash = FilePath::fromUserInput(QStandardPaths::findExecutable("bash.exe"));
|
|
||||||
if (bash.exists())
|
|
||||||
shells << bash;
|
|
||||||
|
|
||||||
FilePath git_bash = FilePath::fromUserInput(QStandardPaths::findExecutable("git.exe"));
|
|
||||||
if (git_bash.exists())
|
|
||||||
shells << git_bash.parentDir().parentDir().pathAppended("usr/bin/bash.exe");
|
|
||||||
|
|
||||||
FilePath msys2_bash = FilePath::fromUserInput(QStandardPaths::findExecutable("msys2.exe"));
|
|
||||||
if (msys2_bash.exists())
|
|
||||||
shells << msys2_bash.parentDir().pathAppended("usr/bin/bash.exe");
|
|
||||||
|
|
||||||
return shells;
|
|
||||||
} else {
|
|
||||||
FilePath shellsFile = FilePath::fromString("/etc/shells");
|
|
||||||
const auto shellFileContent = shellsFile.fileContents();
|
|
||||||
QTC_ASSERT_EXPECTED(shellFileContent, return {});
|
|
||||||
|
|
||||||
QString shellFileContentString = QString::fromUtf8(*shellFileContent);
|
|
||||||
|
|
||||||
// Filter out comments ...
|
|
||||||
const QStringList lines
|
|
||||||
= Utils::filtered(shellFileContentString.split('\n', Qt::SkipEmptyParts),
|
|
||||||
[](const QString &line) { return !line.trimmed().startsWith('#'); });
|
|
||||||
|
|
||||||
// Convert lines to file paths ...
|
|
||||||
const FilePaths shells = Utils::transform(lines, [](const QString &line) {
|
|
||||||
return FilePath::fromUserInput(line.trimmed());
|
|
||||||
});
|
|
||||||
|
|
||||||
// ... and filter out non-existing shells.
|
|
||||||
return Utils::filtered(shells, [](const FilePath &shell) { return shell.exists(); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TerminalPane::TerminalPane(QObject *parent)
|
TerminalPane::TerminalPane(QObject *parent)
|
||||||
: Core::IOutputPane(parent)
|
: Core::IOutputPane(parent)
|
||||||
{
|
{
|
||||||
@@ -92,32 +39,30 @@ TerminalPane::TerminalPane(QObject *parent)
|
|||||||
removeTab(m_tabWidget->currentIndex());
|
removeTab(m_tabWidget->currentIndex());
|
||||||
});
|
});
|
||||||
|
|
||||||
//Core::Command *cmd = Core::ActionManager::registerAction(m_newTerminal, Constants::STOP);
|
|
||||||
//cmd->setDescription(m_newTerminal->toolTip());
|
|
||||||
|
|
||||||
m_newTerminalButton = new QToolButton();
|
m_newTerminalButton = new QToolButton();
|
||||||
|
|
||||||
QMenu *shellMenu = new QMenu(m_newTerminalButton);
|
QMenu *shellMenu = new QMenu(m_newTerminalButton);
|
||||||
|
Internal::ShellModel *shellModel = new Internal::ShellModel(shellMenu);
|
||||||
|
connect(shellMenu, &QMenu::aboutToShow, shellMenu, [shellMenu, shellModel, pane = this] {
|
||||||
|
shellMenu->clear();
|
||||||
|
|
||||||
const FilePaths shells = availableShells();
|
const auto addItems = [shellMenu, pane](const QList<Internal::ShellModelItem> &items) {
|
||||||
|
for (const Internal::ShellModelItem &item : items) {
|
||||||
|
QAction *action = new QAction(item.icon, item.name, shellMenu);
|
||||||
|
|
||||||
QFileIconProvider iconProvider;
|
connect(action, &QAction::triggered, action, [item, pane]() {
|
||||||
|
pane->openTerminal(item.openParameters);
|
||||||
|
});
|
||||||
|
|
||||||
// Create an action for each available shell ...
|
shellMenu->addAction(action);
|
||||||
for (const FilePath &shell : shells) {
|
}
|
||||||
const QIcon icon = iconProvider.icon(shell.toFileInfo());
|
};
|
||||||
|
|
||||||
QAction *action = new QAction(icon, shell.toUserOutput(), shellMenu);
|
addItems(shellModel->local());
|
||||||
action->setData(shell.toVariant());
|
shellMenu->addSection(Tr::tr("Devices"));
|
||||||
shellMenu->addAction(action);
|
addItems(shellModel->remote());
|
||||||
}
|
|
||||||
connect(shellMenu, &QMenu::triggered, this, [this](QAction *action) {
|
|
||||||
openTerminal(
|
|
||||||
Utils::Terminal::OpenTerminalParameters{CommandLine{FilePath::fromVariant(action->data()), {}},
|
|
||||||
std::nullopt,
|
|
||||||
std::nullopt,
|
|
||||||
Utils::Terminal::ExitBehavior::Close});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
m_newTerminal.setMenu(shellMenu);
|
m_newTerminal.setMenu(shellMenu);
|
||||||
|
|
||||||
m_newTerminalButton->setDefaultAction(&m_newTerminal);
|
m_newTerminalButton->setDefaultAction(&m_newTerminal);
|
||||||
@@ -132,6 +77,8 @@ void TerminalPane::openTerminal(const Utils::Terminal::OpenTerminalParameters &p
|
|||||||
m_tabWidget->setCurrentIndex(
|
m_tabWidget->setCurrentIndex(
|
||||||
m_tabWidget->addTab(new TerminalWidget(m_tabWidget, parameters), Tr::tr("Terminal")));
|
m_tabWidget->addTab(new TerminalWidget(m_tabWidget, parameters), Tr::tr("Terminal")));
|
||||||
|
|
||||||
|
m_tabWidget->currentWidget()->setFocus();
|
||||||
|
|
||||||
m_closeTerminal.setEnabled(m_tabWidget->count() > 1);
|
m_closeTerminal.setEnabled(m_tabWidget->count() > 1);
|
||||||
emit navigateStateUpdate();
|
emit navigateStateUpdate();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user