diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index d0b48075683..df27e0404b9 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -18,10 +18,12 @@ struct HooksPrivate }) , m_createTerminalProcessInterfaceHook( []() -> ProcessInterface * { return new Internal::TerminalImpl(); }) + , m_getTerminalCommandsForDevicesHook([]() -> QList { return {}; }) {} Hooks::OpenTerminalHook m_openTerminalHook; Hooks::CreateTerminalProcessInterfaceHook m_createTerminalProcessInterfaceHook; + Hooks::GetTerminalCommandsForDevicesHook m_getTerminalCommandsForDevicesHook; }; Hooks &Hooks::instance() @@ -40,9 +42,15 @@ Hooks::OpenTerminalHook &Hooks::openTerminalHook() { return d->m_openTerminalHook; } + Hooks::CreateTerminalProcessInterfaceHook &Hooks::createTerminalProcessInterfaceHook() { return d->m_createTerminalProcessInterfaceHook; } +Hooks::GetTerminalCommandsForDevicesHook &Hooks::getTerminalCommandsForDevicesHook() +{ + return d->m_getTerminalCommandsForDevicesHook; +} + } // namespace Utils::Terminal diff --git a/src/libs/utils/terminalhooks.h b/src/libs/utils/terminalhooks.h index 57012afc903..492c53da81f 100644 --- a/src/libs/utils/terminalhooks.h +++ b/src/libs/utils/terminalhooks.h @@ -48,19 +48,27 @@ struct OpenTerminalParameters ExitBehavior m_exitBehavior{ExitBehavior::Close}; }; +struct NameAndCommandLine +{ + QString name; + CommandLine commandLine; +}; + class QTCREATOR_UTILS_EXPORT Hooks { public: using OpenTerminalHook = Hook; using CreateTerminalProcessInterfaceHook = Hook; + using GetTerminalCommandsForDevicesHook = Hook>; public: static Hooks &instance(); + ~Hooks(); OpenTerminalHook &openTerminalHook(); CreateTerminalProcessInterfaceHook &createTerminalProcessInterfaceHook(); + GetTerminalCommandsForDevicesHook &getTerminalCommandsForDevicesHook(); - ~Hooks(); private: Hooks(); std::unique_ptr d; diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index 9f8e0594fd8..c254afcd7e7 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -457,6 +458,19 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique QList { + QList result; + for (const IDevice::ConstPtr device : d->devices) { + if (device->type() == Constants::DESKTOP_DEVICE_TYPE) + continue; + const std::optional terminalCommand = device->terminalCommand({}, {}); + if (terminalCommand) + result << Terminal::NameAndCommandLine{device->displayName(), *terminalCommand}; + } + return result; + }); } DeviceManager::~DeviceManager() diff --git a/src/plugins/terminal/CMakeLists.txt b/src/plugins/terminal/CMakeLists.txt index 14f6d844b36..8303fdc1770 100644 --- a/src/plugins/terminal/CMakeLists.txt +++ b/src/plugins/terminal/CMakeLists.txt @@ -1,6 +1,5 @@ add_qtc_plugin(Terminal - SKIP_TRANSLATION PLUGIN_DEPENDS Core DEPENDS libvterm ptyqt SOURCES @@ -13,5 +12,6 @@ add_qtc_plugin(Terminal terminalsettings.cpp terminalsettings.h terminalsettingspage.cpp terminalsettingspage.h scrollback.h scrollback.cpp + shellmodel.cpp shellmodel.h keys.cpp keys.h ) diff --git a/src/plugins/terminal/shellmodel.cpp b/src/plugins/terminal/shellmodel.cpp new file mode 100644 index 00000000000..cd901fbb6e8 --- /dev/null +++ b/src/plugins/terminal/shellmodel.cpp @@ -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 +#include +#include + +#include +#include + +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 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 ShellModel::local() const +{ + return d->localShells; +} + +QList ShellModel::remote() const +{ + const auto deviceCmds = Utils::Terminal::Hooks::instance().getTerminalCommandsForDevicesHook()(); + + const QList deviceItems + = Utils::transform(deviceCmds, + [](const Utils::Terminal::NameAndCommandLine &item) -> ShellModelItem { + return ShellModelItem{item.name, {}, {item.commandLine, {}, {}}}; + }); + + return deviceItems; +} + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/shellmodel.h b/src/plugins/terminal/shellmodel.h new file mode 100644 index 00000000000..272f3fcd394 --- /dev/null +++ b/src/plugins/terminal/shellmodel.h @@ -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 + +#include +#include +#include + +#include + +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 local() const; + QList remote() const; + +private: + std::unique_ptr d; +}; + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/terminalpane.cpp b/src/plugins/terminal/terminalpane.cpp index a355ca02725..f66e6a51715 100644 --- a/src/plugins/terminal/terminalpane.cpp +++ b/src/plugins/terminal/terminalpane.cpp @@ -3,6 +3,7 @@ #include "terminalpane.h" +#include "shellmodel.h" #include "terminaltr.h" #include "terminalwidget.h" @@ -13,7 +14,6 @@ #include #include -#include #include #include @@ -21,59 +21,6 @@ namespace Terminal { 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) : Core::IOutputPane(parent) { @@ -92,32 +39,30 @@ TerminalPane::TerminalPane(QObject *parent) removeTab(m_tabWidget->currentIndex()); }); - //Core::Command *cmd = Core::ActionManager::registerAction(m_newTerminal, Constants::STOP); - //cmd->setDescription(m_newTerminal->toolTip()); - m_newTerminalButton = new QToolButton(); 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 &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 ... - for (const FilePath &shell : shells) { - const QIcon icon = iconProvider.icon(shell.toFileInfo()); + shellMenu->addAction(action); + } + }; - QAction *action = new QAction(icon, shell.toUserOutput(), shellMenu); - action->setData(shell.toVariant()); - shellMenu->addAction(action); - } - 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}); + addItems(shellModel->local()); + shellMenu->addSection(Tr::tr("Devices")); + addItems(shellModel->remote()); }); + m_newTerminal.setMenu(shellMenu); m_newTerminalButton->setDefaultAction(&m_newTerminal); @@ -132,6 +77,8 @@ void TerminalPane::openTerminal(const Utils::Terminal::OpenTerminalParameters &p m_tabWidget->setCurrentIndex( m_tabWidget->addTab(new TerminalWidget(m_tabWidget, parameters), Tr::tr("Terminal"))); + m_tabWidget->currentWidget()->setFocus(); + m_closeTerminal.setEnabled(m_tabWidget->count() > 1); emit navigateStateUpdate(); }