feat: add Konsole recipe source and patches

This commit is contained in:
2026-05-07 07:54:52 +01:00
parent 171a96c6af
commit ab85eb7b3d
632 changed files with 713138 additions and 3 deletions
@@ -0,0 +1,555 @@
/*
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "Application.h"
// Qt
#include <QApplication>
#include <QCommandLineParser>
#include <QDir>
#include <QFileInfo>
#include <QHash>
#include <QStandardPaths>
#include <QTimer>
// KDE
#include <KActionCollection>
#if HAVE_DBUS
#include <KGlobalAccel>
#endif
#include <KLocalizedString>
// Konsole
#include "KonsoleSettings.h"
#include "MainWindow.h"
#include "ShellCommand.h"
#include "ViewManager.h"
#include "WindowSystemInfo.h"
#include "profile/ProfileCommandParser.h"
#include "profile/ProfileManager.h"
#include "session/Session.h"
#include "session/SessionManager.h"
#include "widgets/ViewContainer.h"
#include "pluginsystem/IKonsolePlugin.h"
using namespace Konsole;
Application::Application(QSharedPointer<QCommandLineParser> parser, const QStringList &customCommand)
: _backgroundInstance(nullptr)
, m_parser(parser)
, m_customCommand(customCommand)
{
m_pluginManager.loadAllPlugins();
}
void Application::populateCommandLineParser(QCommandLineParser *parser)
{
const auto options = QVector<QCommandLineOption>{
{{QStringLiteral("profile")}, i18nc("@info:shell", "Name of profile to use for new Konsole instance"), QStringLiteral("name")},
{{QStringLiteral("layout")}, i18nc("@info:shell", "json layoutfile to be loaded to use for new Konsole instance"), QStringLiteral("file")},
{{QStringLiteral("builtin-profile")}, i18nc("@info:shell", "Use the built-in profile instead of the default profile")},
{{QStringLiteral("workdir")}, i18nc("@info:shell", "Set the initial working directory of the new tab or window to 'dir'"), QStringLiteral("dir")},
{{QStringLiteral("hold"), QStringLiteral("noclose")}, i18nc("@info:shell", "Do not close the initial session automatically when it ends.")},
// BR: 373440
{{QStringLiteral("new-tab")},
i18nc("@info:shell",
"Create a new tab in an existing window rather than creating a new window ('Run all Konsole windows in a single process' must be enabled)")},
{{QStringLiteral("tabs-from-file")}, i18nc("@info:shell", "Create tabs as specified in given tabs configuration file"), QStringLiteral("file")},
{{QStringLiteral("background-mode")},
i18nc("@info:shell", "Start Konsole in the background and bring to the front when Ctrl+Shift+F12 (by default) is pressed")},
{{QStringLiteral("separate"), QStringLiteral("nofork")}, i18nc("@info:shell", "Run in a separate process")},
{{QStringLiteral("show-menubar")}, i18nc("@info:shell", "Show the menubar, overriding the default setting")},
{{QStringLiteral("hide-menubar")}, i18nc("@info:shell", "Hide the menubar, overriding the default setting")},
{{QStringLiteral("show-tabbar")}, i18nc("@info:shell", "Show the tabbar, overriding the default setting")},
{{QStringLiteral("hide-tabbar")}, i18nc("@info:shell", "Hide the tabbar, overriding the default setting")},
{{QStringLiteral("fullscreen")}, i18nc("@info:shell", "Start Konsole in fullscreen mode")},
{{QStringLiteral("notransparency")}, i18nc("@info:shell", "Disable transparent backgrounds, even if the system supports them.")},
{{QStringLiteral("list-profiles")}, i18nc("@info:shell", "List the available profiles")},
{{QStringLiteral("list-profile-properties")}, i18nc("@info:shell", "List all the profile properties names and their type (for use with -p)")},
{{QStringLiteral("p")}, i18nc("@info:shell", "Change the value of a profile property."), QStringLiteral("property=value")},
{{QStringLiteral("e")},
i18nc("@info:shell", "Command to execute. This option will catch all following arguments, so use it as the last option."),
QStringLiteral("cmd")},
{{QStringLiteral("force-reuse")},
i18nc("@info:shell", "Force re-using the existing instance even if it breaks functionality, e. g. --new-tab. Mostly for debugging.")},
};
for (const auto &option : options) {
parser->addOption(option);
}
parser->addPositionalArgument(QStringLiteral("[args]"), i18nc("@info:shell", "Arguments passed to command"));
// Add a no-op compatibility option to make Konsole compatible with
// Debian's policy on X terminal emulators.
// -T is technically meant to set a title, that is not really meaningful
// for Konsole as we have multiple user-facing options controlling
// the title and overriding whatever is set elsewhere.
// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=532029
// https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s11.8.3
// --title is used by the VirtualBox Guest Additions installer
auto titleOption =
QCommandLineOption({QStringLiteral("T"), QStringLiteral("title")}, QStringLiteral("Debian policy compatibility, not used"), QStringLiteral("value"));
titleOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser->addOption(titleOption);
}
QStringList Application::getCustomCommand(QStringList &args)
{
int i = args.indexOf(QStringLiteral("-e"));
QStringList customCommand;
if ((0 < i) && (i < (args.size() - 1))) {
// -e was specified with at least one extra argument
// if -e was specified without arguments, QCommandLineParser will deal
// with that
args.removeAt(i);
while (args.size() > i) {
customCommand << args.takeAt(i);
}
}
return customCommand;
}
Application::~Application()
{
SessionManager::instance()->closeAllSessions();
}
MainWindow *Application::newMainWindow()
{
WindowSystemInfo::HAVE_TRANSPARENCY = !m_parser->isSet(QStringLiteral("notransparency"));
auto window = new MainWindow();
connect(window, &Konsole::MainWindow::newWindowRequest, this, &Konsole::Application::createWindow);
connect(window,
&Konsole::MainWindow::terminalsDetached,
this,
[this, window](ViewSplitter *splitter, const QHash<TerminalDisplay *, Session *> &sessionsMap) {
detachTerminals(window, splitter, sessionsMap);
});
m_pluginManager.registerMainWindow(window);
return window;
}
void Application::createWindow(const Profile::Ptr &profile, const QString &directory)
{
MainWindow *window = newMainWindow();
window->createSession(profile, directory);
window->show();
}
void Application::detachTerminals(MainWindow *currentWindow, ViewSplitter *splitter, const QHash<TerminalDisplay *, Session *> &sessionsMap)
{
MainWindow *window = newMainWindow();
ViewManager *manager = window->viewManager();
const QList<TerminalDisplay *> displays = splitter->findChildren<TerminalDisplay *>();
for (TerminalDisplay *terminal : displays) {
manager->attachView(terminal, sessionsMap[terminal]);
}
manager->activeContainer()->addSplitter(splitter);
window->show();
window->resize(currentWindow->width(), currentWindow->height());
window->move(QCursor::pos());
}
int Application::newInstance()
{
// handle session management
// returns from processWindowArgs(args, createdNewMainWindow)
// if a new window was created
bool createdNewMainWindow = false;
// check for arguments to print help or other information to the
// terminal, quit if such an argument was found
if (processHelpArgs()) {
return 0;
}
// create a new window or use an existing one
MainWindow *window = processWindowArgs(createdNewMainWindow);
if (m_parser->isSet(QStringLiteral("tabs-from-file"))) {
// create new session(s) as described in file
if (!processTabsFromFileArgs(window)) {
return 0;
}
}
// select profile to use
Profile::Ptr baseProfile = processProfileSelectArgs();
// process various command-line options which cause a property of the
// selected profile to be changed
Profile::Ptr newProfile = processProfileChangeArgs(baseProfile);
// if layout file is enable load it and create session from definitions,
// else create new session
if (m_parser->isSet(QStringLiteral("layout"))) {
window->viewManager()->loadLayout(m_parser->value(QStringLiteral("layout")));
} else {
Session *session = window->createSession(newProfile, QString());
const QString workingDir = m_parser->value(QStringLiteral("workdir"));
if (!workingDir.isEmpty()) {
session->setInitialWorkingDirectory(workingDir);
}
if (m_parser->isSet(QStringLiteral("noclose"))) {
session->setAutoClose(false);
}
}
// if the background-mode argument is supplied, start the background
// session ( or bring to the front if it already exists )
if (m_parser->isSet(QStringLiteral("background-mode"))) {
startBackgroundMode(window);
} else {
// Qt constrains top-level windows which have not been manually
// resized (via QWidget::resize()) to a maximum of 2/3rds of the
// screen size.
//
// This means that the terminal display might not get the width/
// height it asks for. To work around this, the widget must be
// manually resized to its sizeHint().
//
// This problem only affects the first time the application is run.
// run. After that KMainWindow will have manually resized the
// window to its saved size at this point (so the Qt::WA_Resized
// attribute will be set)
// If not restoring size from last time or only adding new tab,
// resize window to chosen profile size (see Bug:345403)
if (createdNewMainWindow) {
QTimer::singleShot(0, window, &MainWindow::show);
} else {
window->setWindowState(window->windowState() & (~Qt::WindowMinimized | Qt::WindowActive));
window->show();
window->activateWindow();
}
}
return 1;
}
/* Documentation for tab file:
*
* ;; is the token separator
* # at the beginning of line results in line being ignored.
* supported tokens: title, command, profile and workdir
*
* Note that the title is static and the tab will close when the
* command is complete (do not use --noclose). You can start new tabs.
*
* Example below will create 6 tabs as listed and a 7th default tab
title: This is the title;; command: ssh localhost
title: This is the title;; command: ssh localhost;; profile: Shell
title: Top this!;; command: top
title: mc this!;; command: mc;; workdir: /tmp
#this line is comment
command: ssh localhost
profile: Shell
*/
bool Application::processTabsFromFileArgs(MainWindow *window)
{
// Open tab configuration file
const QString tabsFileName(m_parser->value(QStringLiteral("tabs-from-file")));
QFile tabsFile(tabsFileName);
if (!tabsFile.open(QFile::ReadOnly)) {
qWarning() << "ERROR: Cannot open tabs file " << tabsFileName.toLocal8Bit().data();
return false;
}
unsigned int sessions = 0;
while (!tabsFile.atEnd()) {
QString lineString(QString::fromUtf8(tabsFile.readLine()).trimmed());
if ((lineString.isEmpty()) || (lineString[0] == QLatin1Char('#'))) {
continue;
}
QHash<QString, QString> lineTokens;
QStringList lineParts = lineString.split(QStringLiteral(";;"), Qt::SkipEmptyParts);
for (int i = 0; i < lineParts.size(); ++i) {
QString key = lineParts.at(i).section(QLatin1Char(':'), 0, 0).trimmed().toLower();
QString value = lineParts.at(i).section(QLatin1Char(':'), 1, -1).trimmed();
lineTokens[key] = value;
}
// should contain at least one of 'command' and 'profile'
if (lineTokens.contains(QStringLiteral("command")) || lineTokens.contains(QStringLiteral("profile"))) {
createTabFromArgs(window, lineTokens);
sessions++;
} else {
qWarning() << "Each line should contain at least one of 'command' and 'profile'.";
}
}
tabsFile.close();
if (sessions < 1) {
qWarning() << "No valid lines found in " << tabsFileName.toLocal8Bit().data();
return false;
}
return true;
}
void Application::createTabFromArgs(MainWindow *window, const QHash<QString, QString> &tokens)
{
const QString &title = tokens[QStringLiteral("title")];
const QString &command = tokens[QStringLiteral("command")];
const QString &profile = tokens[QStringLiteral("profile")];
const QColor &color = tokens[QStringLiteral("tabcolor")];
Profile::Ptr baseProfile;
if (!profile.isEmpty()) {
baseProfile = ProfileManager::instance()->loadProfile(profile);
}
if (!baseProfile) {
// fallback to default profile
baseProfile = ProfileManager::instance()->defaultProfile();
}
Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile));
newProfile->setHidden(true);
// FIXME: the method of determining whether to use newProfile does not
// scale well when we support more fields in the future
bool shouldUseNewProfile = false;
if (!command.isEmpty()) {
newProfile->setProperty(Profile::Command, command);
newProfile->setProperty(Profile::Arguments, command.split(QLatin1Char(' ')));
shouldUseNewProfile = true;
}
if (!title.isEmpty()) {
newProfile->setProperty(Profile::LocalTabTitleFormat, title);
newProfile->setProperty(Profile::RemoteTabTitleFormat, title);
shouldUseNewProfile = true;
}
// For tab color support
if (color.isValid()) {
newProfile->setProperty(Profile::TabColor, color);
shouldUseNewProfile = true;
}
// Create the new session
Profile::Ptr theProfile = shouldUseNewProfile ? newProfile : baseProfile;
Session *session = window->createSession(theProfile, QString());
const QString wdirOptionName(QStringLiteral("workdir"));
auto it = tokens.constFind(wdirOptionName);
const QString workingDirectory = it != tokens.cend() ? it.value() : m_parser->value(wdirOptionName);
if (!workingDirectory.isEmpty()) {
session->setInitialWorkingDirectory(workingDirectory);
}
if (m_parser->isSet(QStringLiteral("noclose"))) {
session->setAutoClose(false);
}
if (!window->testAttribute(Qt::WA_Resized)) {
window->resize(window->sizeHint());
}
// FIXME: this ugly hack here is to make the session start running, so that
// its tab title is displayed as expected.
//
// This is another side effect of the commit fixing BKO 176902.
window->show();
window->hide();
}
// Creates a new Konsole window.
// If --new-tab is given, use existing window.
MainWindow *Application::processWindowArgs(bool &createdNewMainWindow)
{
MainWindow *window = nullptr;
if (m_parser->isSet(QStringLiteral("new-tab"))) {
const QList<QWidget *> list = QApplication::topLevelWidgets();
for (auto it = list.crbegin(), endIt = list.crend(); it != endIt; ++it) {
window = qobject_cast<MainWindow *>(*it);
if (window) {
break;
}
}
}
if (window == nullptr) {
createdNewMainWindow = true;
window = newMainWindow();
// override default menubar visibility
if (m_parser->isSet(QStringLiteral("show-menubar"))) {
window->setMenuBarInitialVisibility(true);
}
if (m_parser->isSet(QStringLiteral("hide-menubar"))) {
window->setMenuBarInitialVisibility(false);
}
if (m_parser->isSet(QStringLiteral("fullscreen"))) {
window->viewFullScreen(true);
}
if (m_parser->isSet(QStringLiteral("show-tabbar"))) {
window->viewManager()->setNavigationVisibility(ViewManager::AlwaysShowNavigation);
} else if (m_parser->isSet(QStringLiteral("hide-tabbar"))) {
window->viewManager()->setNavigationVisibility(ViewManager::AlwaysHideNavigation);
}
}
return window;
}
// Loads a profile.
// If --profile <name> is given, loads profile <name>.
// If --builtin-profile is given, loads built-in profile.
// Else loads the default profile.
Profile::Ptr Application::processProfileSelectArgs()
{
if (m_parser->isSet(QStringLiteral("profile"))) {
Profile::Ptr profile = ProfileManager::instance()->loadProfile(m_parser->value(QStringLiteral("profile")));
if (profile) {
return profile;
}
} else if (m_parser->isSet(QStringLiteral("builtin-profile"))) {
// no need to check twice: built-in and default profiles are always available
return ProfileManager::instance()->builtinProfile();
}
return ProfileManager::instance()->defaultProfile();
}
bool Application::processHelpArgs()
{
if (m_parser->isSet(QStringLiteral("list-profiles"))) {
listAvailableProfiles();
return true;
} else if (m_parser->isSet(QStringLiteral("list-profile-properties"))) {
listProfilePropertyInfo();
return true;
}
return false;
}
void Application::listAvailableProfiles()
{
const QStringList paths = ProfileManager::instance()->availableProfilePaths();
for (const QString &path : paths) {
QFileInfo info(path);
printf("%s\n", info.completeBaseName().toLocal8Bit().constData());
}
}
void Application::listProfilePropertyInfo()
{
const std::vector<std::string> &properties = Profile::propertiesInfoList();
for (const auto &prop : properties) {
printf("%s\n", prop.c_str());
}
}
Profile::Ptr Application::processProfileChangeArgs(Profile::Ptr baseProfile)
{
bool shouldUseNewProfile = false;
Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile));
newProfile->setHidden(true);
// temporary changes to profile options specified on the command line
const QStringList profileProperties = m_parser->values(QStringLiteral("p"));
for (const QString &value : profileProperties) {
ProfileCommandParser parser;
newProfile->assignProperties(parser.parse(value));
shouldUseNewProfile = true;
}
// run a custom command
if (!m_customCommand.isEmpty()) {
// Example: konsole -e man ls
QString commandExec = m_customCommand[0];
QStringList commandArguments(m_customCommand);
if ((m_customCommand.size() == 1) && (QStandardPaths::findExecutable(commandExec).isEmpty())) {
// Example: konsole -e "man ls"
ShellCommand shellCommand(commandExec);
commandExec = shellCommand.command();
commandArguments = shellCommand.arguments();
}
if (commandExec.startsWith(QLatin1String("./"))) {
commandExec = QDir::currentPath() + commandExec.mid(1);
}
newProfile->setProperty(Profile::Command, commandExec);
newProfile->setProperty(Profile::Arguments, commandArguments);
shouldUseNewProfile = true;
}
if (shouldUseNewProfile) {
return newProfile;
}
return baseProfile;
}
void Application::startBackgroundMode(MainWindow *window)
{
if (_backgroundInstance != nullptr) {
return;
}
#if HAVE_DBUS
KActionCollection *collection = window->actionCollection();
QAction *action = collection->addAction(QStringLiteral("toggle-background-window"));
action->setObjectName(QStringLiteral("Konsole Background Mode"));
action->setText(i18nc("@item", "Toggle Background Window"));
KGlobalAccel::self()->setGlobalShortcut(action, QKeySequence(Konsole::ACCEL | Qt::Key_F12));
connect(action, &QAction::triggered, this, &Application::toggleBackgroundInstance);
#endif
_backgroundInstance = window;
}
void Application::toggleBackgroundInstance()
{
Q_ASSERT(_backgroundInstance);
if (!_backgroundInstance->isVisible()) {
_backgroundInstance->show();
// ensure that the active terminal display has the focus. Without
// this, an odd problem occurred where the focus widget would change
// each time the background instance was shown
_backgroundInstance->setFocus();
} else {
_backgroundInstance->hide();
}
}
void Application::slotActivateRequested(QStringList args, const QString & /*workingDir*/)
{
// QCommandLineParser expects the first argument to be the executable name
// In the current version it just strips it away
args.prepend(qApp->applicationFilePath());
m_customCommand = getCustomCommand(args);
// We can't re-use QCommandLineParser instances, it preserves earlier parsed values
auto parser = new QCommandLineParser;
populateCommandLineParser(parser);
parser->parse(args);
m_parser.reset(parser);
newInstance();
}
#include "moc_Application.cpp"
@@ -0,0 +1,89 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef APPLICATION_H
#define APPLICATION_H
// Qt
#include <QCommandLineParser>
// Konsole
#include "konsoleapp_export.h"
#include "pluginsystem/PluginManager.h"
#include "profile/Profile.h"
#include "terminalDisplay/TerminalDisplay.h"
#include "widgets/ViewSplitter.h"
namespace Konsole
{
class MainWindow;
class Session;
class Profile;
/**
* The Konsole Application.
*
* The application consists of one or more main windows and a set of
* factories to create new sessions and views.
*
* To create a new main window with a default terminal session, call
* the newInstance(). Empty main windows can be created using newMainWindow().
*
* The factory used to create new terminal sessions can be retrieved using
* the sessionManager() accessor.
*/
class KONSOLEAPP_EXPORT Application : public QObject
{
Q_OBJECT
public:
/** Constructs a new Konsole application. */
explicit Application(QSharedPointer<QCommandLineParser> parser, const QStringList &customCommand);
static void populateCommandLineParser(QCommandLineParser *parser);
static QStringList getCustomCommand(QStringList &args);
~Application() override;
/** Creates a new main window and opens a default terminal session */
int newInstance();
/**
* Creates a new, empty main window and connects to its newSessionRequest()
* and newWindowRequest() signals to trigger creation of new sessions or
* windows when then they are emitted.
*/
MainWindow *newMainWindow();
private Q_SLOTS:
void createWindow(const QExplicitlySharedDataPointer<Profile> &profile, const QString &directory);
void detachTerminals(MainWindow *currentWindow, ViewSplitter *splitter, const QHash<TerminalDisplay *, Session *> &sessionsMap);
void toggleBackgroundInstance();
public Q_SLOTS:
void slotActivateRequested(QStringList args, const QString &workingDir);
private:
Q_DISABLE_COPY(Application)
void listAvailableProfiles();
void listProfilePropertyInfo();
void startBackgroundMode(MainWindow *window);
bool processHelpArgs();
MainWindow *processWindowArgs(bool &createdNewMainWindow);
QExplicitlySharedDataPointer<Profile> processProfileSelectArgs();
QExplicitlySharedDataPointer<Profile> processProfileChangeArgs(QExplicitlySharedDataPointer<Profile> baseProfile);
bool processTabsFromFileArgs(MainWindow *window);
void createTabFromArgs(MainWindow *window, const QHash<QString, QString> &);
MainWindow *_backgroundInstance;
QSharedPointer<QCommandLineParser> m_parser;
QStringList m_customCommand;
PluginManager m_pluginManager;
};
}
#endif // APPLICATION_H
@@ -0,0 +1,166 @@
/* This file was part of the KDE libraries
SPDX-FileCopyrightText: 2002 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Born as kdelibs/kio/kfile/kfilebookmarkhandler.cpp
// Own
#include "BookmarkHandler.h"
// Qt
#include <QDir>
#include <QFileInfo>
#include <QStandardPaths>
// KDE
#include <KLocalizedString>
#include <KShell>
// Konsole
#include "BookmarkMenu.h"
using namespace Konsole;
BookmarkHandler::BookmarkHandler(KActionCollection *collection, QMenu *menu, bool toplevel, QObject *parent)
: QObject(parent)
, KBookmarkOwner()
, _menu(menu)
, _file(QString())
, _toplevel(toplevel)
, _activeView(nullptr)
, _views(QList<ViewProperties *>())
{
setObjectName(QStringLiteral("BookmarkHandler"));
_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("konsole/bookmarks.xml"));
if (_file.isEmpty()) {
_file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/konsole");
QDir().mkpath(_file);
_file += QStringLiteral("/bookmarks.xml");
}
KBookmarkManager *manager = new KBookmarkManager(_file, this);
// This constructor is only called with toplevel as true; regardless the
// fourth argument can not be nullptr as it will crash in BookmarkMenu
// TODO: remove the toplevel boolean argument in API
auto *bookmarkMenu = new BookmarkMenu(manager, this, _menu, collection);
bookmarkMenu->setParent(this);
}
BookmarkHandler::~BookmarkHandler() = default;
void BookmarkHandler::openBookmark(const KBookmark &bm, Qt::MouseButtons, Qt::KeyboardModifiers)
{
Q_EMIT openUrl(bm.url());
}
void BookmarkHandler::openFolderinTabs(const KBookmarkGroup &group)
{
Q_EMIT openUrls(group.groupUrlList());
}
bool BookmarkHandler::enableOption(BookmarkOption option) const
{
if (option == ShowAddBookmark || option == ShowEditBookmark) {
return _toplevel;
}
return KBookmarkOwner::enableOption(option);
}
QUrl BookmarkHandler::currentUrl() const
{
return urlForView(_activeView);
}
QUrl BookmarkHandler::urlForView(ViewProperties *view) const
{
if (view != nullptr) {
return view->url();
}
return {};
}
QString BookmarkHandler::currentTitle() const
{
return titleForView(_activeView);
}
QString BookmarkHandler::titleForView(ViewProperties *view) const
{
const QUrl &url = view != nullptr ? view->url() : QUrl();
if (url.isLocalFile()) {
QString path = url.path();
path = KShell::tildeExpand(path);
path = QFileInfo(path).completeBaseName();
return path;
}
if (!url.host().isEmpty()) {
if (!url.userName().isEmpty()) {
return i18nc("@item:inmenu The user's name and host they are connected to via ssh", "%1 on %2", url.userName(), url.host());
}
return i18nc("@item:inmenu The host the user is connected to via ssh", "%1", url.host());
}
return url.toDisplayString();
}
QString BookmarkHandler::currentIcon() const
{
return iconForView(_activeView);
}
QString BookmarkHandler::iconForView(ViewProperties *view) const
{
if (view != nullptr) {
return view->icon().name();
}
return {};
}
QList<KBookmarkOwner::FutureBookmark> BookmarkHandler::currentBookmarkList() const
{
QList<KBookmarkOwner::FutureBookmark> list;
list.reserve(_views.size());
const QList<ViewProperties *> views = _views;
for (ViewProperties *view : views) {
list << KBookmarkOwner::FutureBookmark(titleForView(view), urlForView(view), iconForView(view));
}
return list;
}
bool BookmarkHandler::supportsTabs() const
{
return true;
}
void BookmarkHandler::setViews(const QList<ViewProperties *> &views)
{
_views = views;
}
QList<ViewProperties *> BookmarkHandler::views() const
{
return _views;
}
void BookmarkHandler::setActiveView(ViewProperties *view)
{
_activeView = view;
}
ViewProperties *BookmarkHandler::activeView() const
{
return _activeView;
}
#include "moc_BookmarkHandler.cpp"
@@ -0,0 +1,118 @@
/* This file was part of the KDE libraries
SPDX-FileCopyrightText: 2002 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Born as kdelibs/kio/kfile/kfilebookmarkhandler.h
#ifndef BOOKMARKHANDLER_H
#define BOOKMARKHANDLER_H
// KDE
#include <KBookmarkManager>
#include <KBookmarkOwner>
// Konsole
#include "ViewProperties.h"
#include "konsoleprivate_export.h"
class QMenu;
class QUrl;
class KActionCollection;
namespace Konsole
{
/**
* This class handles the communication between the bookmark menu and the active session,
* providing a suggested title and URL when the user clicks the "Add Bookmark" item in
* the bookmarks menu.
*
* The bookmark handler is associated with a session controller, which is used to
* determine the working URL of the current session. When the user changes the active
* view, the bookmark handler's controller should be changed using setController()
*
* When the user selects a bookmark, the openUrl() signal is emitted.
*/
class KONSOLEPRIVATE_EXPORT BookmarkHandler : public QObject, public KBookmarkOwner
{
Q_OBJECT
public:
/**
* Constructs a new bookmark handler for Konsole bookmarks.
*
* @param collection The collection which the bookmark menu's actions should be added to
* @param menu The menu which the bookmark actions should be added to
* @param toplevel TODO: Document me
* @param parent The parent object
*/
BookmarkHandler(KActionCollection *collection, QMenu *menu, bool toplevel, QObject *parent);
~BookmarkHandler() override;
QUrl currentUrl() const override;
QString currentTitle() const override;
QString currentIcon() const override;
bool enableOption(BookmarkOption option) const override;
bool supportsTabs() const override;
QList<KBookmarkOwner::FutureBookmark> currentBookmarkList() const override;
void openFolderinTabs(const KBookmarkGroup &group) override;
/**
* Returns the menu which this bookmark handler inserts its actions into.
*/
QMenu *menu() const
{
return _menu;
}
QList<ViewProperties *> views() const;
ViewProperties *activeView() const;
public Q_SLOTS:
/**
*
*/
void setViews(const QList<ViewProperties *> &views);
void setActiveView(ViewProperties *view);
Q_SIGNALS:
/**
* Emitted when the user selects a bookmark from the bookmark menu.
*
* @param url The url of the bookmark which was selected by the user.
*/
void openUrl(const QUrl &url);
/**
* Emitted when the user selects 'Open Folder in Tabs'
* from the bookmark menu.
*
* @param urls The urls of the bookmarks in the folder whose
* 'Open Folder in Tabs' action was triggered
*/
void openUrls(const QList<QUrl> &urls);
private Q_SLOTS:
void openBookmark(const KBookmark &bm, Qt::MouseButtons, Qt::KeyboardModifiers) override;
private:
Q_DISABLE_COPY(BookmarkHandler)
QString titleForView(ViewProperties *view) const;
QUrl urlForView(ViewProperties *view) const;
QString iconForView(ViewProperties *view) const;
QMenu *_menu;
QString _file;
bool _toplevel;
ViewProperties *_activeView;
QList<ViewProperties *> _views;
};
}
#endif // BOOKMARKHANDLER_H
@@ -0,0 +1,65 @@
/* This file was part of the KDE libraries
SPDX-FileCopyrightText: 2019 Tomaz Canabrava <tcanabrava@kde.org>
SPDX-FileCopyrightText: 2019 Martin Sandsmark <martin.sandsmark@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "BookmarkMenu.h"
// KDE
#include <KActionCollection>
#include <KBookmark>
#include <KBookmarkManager>
#include <KBookmarkOwner>
// Qt
#include <QAction>
#include <QMenu>
#include <algorithm> // std::any_of
BookmarkMenu::BookmarkMenu(KBookmarkManager *mgr, KBookmarkOwner *owner, QMenu *parentMenu, KActionCollection *collection)
: KBookmarkMenu(mgr, owner, parentMenu)
{
QAction *bookmarkAction;
collection->addActions(parentMenu->actions());
bookmarkAction = addBookmarkAction();
Q_ASSERT(bookmarkAction);
// We need to hijack the action - note this only hijacks top-level action
disconnect(bookmarkAction, nullptr, this, nullptr);
connect(bookmarkAction, &QAction::triggered, this, &BookmarkMenu::maybeAddBookmark);
#ifndef Q_OS_MACOS // not needed on this platform, Cmd+B (shortcut) is different to Ctrl+B (^B in terminal)
// replace Ctrl+B shortcut for bookmarks only if user hasn't already
// changed the shortcut; however, if the user changed it to Ctrl+B
// this will still get changed to Ctrl+Shift+B
if (bookmarkAction->shortcut() == QKeySequence(Qt::CTRL | Qt::Key_B)) {
collection->setDefaultShortcut(bookmarkAction, Qt::CTRL | Qt::SHIFT | Qt::Key_B);
}
#endif
}
void BookmarkMenu::maybeAddBookmark()
{
// Check for duplicates first
// This only catches duplicates in top-level (ie not sub-folders);
// this is due to only the top-level add_bookmark is hijacked.
const KBookmarkGroup rootGroup = manager()->root();
const QUrl currUrl = owner()->currentUrl();
const auto urlList = rootGroup.groupUrlList();
if (std::any_of(urlList.constBegin(), urlList.constEnd(), [currUrl](const QUrl &url) {
return url == currUrl;
})) {
return;
}
slotAddBookmark();
}
#include "moc_BookmarkMenu.cpp"
@@ -0,0 +1,37 @@
/* This file was part of the KDE libraries
SPDX-FileCopyrightText: 2019 Tomaz Canabrava <tcanabrava@kde.org>
SPDX-FileCopyrightText: 2019 Martin Sandsmark <martin.sandsmark@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BOOKMARKMENU_H
#define BOOKMARKMENU_H
// KDE
#include <KBookmarkMenu>
// Konsole
#include "konsoleprivate_export.h"
class KActionCollection;
/* Hackish hack to mitigate a broken behavior of KBookmarkMenu.
* slotAddBookmark accepts duplicates and it's fragile code,
* that thing really deserves a rewrite.
* the easiest way is to "hijack" it's protected method to public
* and just cast around.
*/
class KONSOLEPRIVATE_EXPORT BookmarkMenu : public KBookmarkMenu
{
Q_OBJECT
public:
BookmarkMenu(KBookmarkManager *mgr, KBookmarkOwner *owner, QMenu *parentMenu, KActionCollection *collection);
private Q_SLOTS:
void maybeAddBookmark();
};
#endif // BOOKMARKMENU_H
@@ -0,0 +1,411 @@
# cmake-options : -DCMAKE_DISABLE_FIND_PACKAGE_LibKonq=TRUE or FALSE; default is FALSE
add_definitions(-DTRANSLATION_DOMAIN=\"konsole\")
### Handle DragonFlyBSD here instead of using __DragonFly__
if(${CMAKE_SYSTEM_NAME} MATCHES "DragonFly")
set(HAVE_OS_DRAGONFLYBSD 1)
else()
set(HAVE_OS_DRAGONFLYBSD 0)
endif()
include(CheckIncludeFiles)
include(ECMAddAppIcon)
configure_file(config-konsole.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/config-konsole.h)
### Tests
if(BUILD_TESTING)
find_package(Qt6Test ${QT_MIN_VERSION} CONFIG REQUIRED)
add_subdirectory(autotests)
add_subdirectory(tests)
endif()
### Security concerns about sendText and runCommand dbus methods being public
option(REMOVE_SENDTEXT_RUNCOMMAND_DBUS_METHODS "Konsole: remove sendText and runCommand dbus methods" OFF)
### Security concerns about reading arbitrary screen positions
option(ENABLE_DECRQCRA "Konsole: enable DEC request checksum rectangular area" OFF)
### Development tools
option(KONSOLE_BUILD_UNI2CHARACTERWIDTH "Konsole: build uni2characterwidth executable" OFF)
set(dbus_xml_srcs)
if(HAVE_DBUS)
### Konsole source files shared between embedded terminal and main application
# qdbuscpp2xml -m Session.h -o org.kde.konsole.Session.xml
# qdbuscpp2xml -M -s ViewManager.h -o org.kde.konsole.Konsole.xml
# Generate dbus .xml files; do not store .xml in source folder
qt_generate_dbus_interface(ViewManager.h org.kde.konsole.Window.xml OPTIONS -m)
qt_add_dbus_adaptor(windowadaptors_SRCS
${CMAKE_CURRENT_BINARY_DIR}/org.kde.konsole.Window.xml
ViewManager.h
Konsole::ViewManager)
# qdbuscpp2xml -m Session.h -o org.kde.konsole.Session.xml
# Generate dbus .xml files; do not store .xml in source folder
qt_generate_dbus_interface(session/Session.h org.kde.konsole.Session.xml OPTIONS -m)
qt_add_dbus_adaptor(
sessionadaptors_SRCS
${CMAKE_CURRENT_BINARY_DIR}/org.kde.konsole.Session.xml
session/Session.h
Konsole::Session
)
set(dbus_xml_srcs
${CMAKE_CURRENT_BINARY_DIR}/org.kde.konsole.Session.xml
${CMAKE_CURRENT_BINARY_DIR}/org.kde.konsole.Window.xml
)
endif()
set(konsole_LIBS
KF6::XmlGui
Qt::Multimedia
Qt::PrintSupport
Qt::Xml
KF6::Notifications
KF6::WindowSystem
KF6::TextWidgets
KF6::GuiAddons
KF6::IconThemes
KF6::Bookmarks
KF6::I18n
KF6::KIOWidgets
KF6::NewStuffCore
)
if(NOT WIN32)
list(APPEND konsole_LIBS
KF6::Pty
)
endif()
if(HAVE_DBUS)
list(APPEND konsole_LIBS
KF6::DBusAddons
KF6::GlobalAccel
)
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
#kinfo_getfile() is in libutil
list(APPEND konsole_LIBS util)
endif()
# TODO: Move this to an internal folder if it grows too much.
# This konsoleprivate_core is the "core" library that depends in nothing from konsole itself
# so we can start to detangle the codebase.
set(konsoleprivate_core_SRCS
ShellCommand.cpp
)
ecm_qt_declare_logging_category(
konsoleprivate_core_SRCS
HEADER konsoledebug.h
IDENTIFIER KonsoleDebug
CATEGORY_NAME org.kde.konsole
DESCRIPTION "Konsole"
EXPORT KONSOLE
)
add_library(konsoleprivate_core STATIC ${konsoleprivate_core_SRCS})
# Needed to link this static lib to shared libs
set_target_properties(konsoleprivate_core PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_libraries(konsoleprivate_core KF6::CoreAddons)
set(konsolehelpers_SRCS
LabelsAligner.cpp
)
add_library(konsolehelpers
STATIC
${konsolehelpers_SRCS}
)
set_target_properties(konsolehelpers PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_libraries(konsolehelpers
Qt::Core
Qt::Widgets
)
add_subdirectory(colorscheme)
add_subdirectory(keyboardtranslator)
add_subdirectory(characters)
add_subdirectory(decoders)
add_subdirectory(plugins)
if(WIN32)
set(ptyqt_srcs ptyqt/iptyprocess.cpp ptyqt/conptyprocess.cpp)
endif()
set(konsoleprivate_SRCS ${windowadaptors_SRCS}
BookmarkHandler.cpp
BookmarkMenu.cpp
CheckableSessionModel.cpp
CopyInputDialog.cpp
Emulation.cpp
EscapeSequenceUrlExtractor.cpp
FontDialog.cpp
HistorySizeDialog.cpp
KeyBindingEditor.cpp
LabelsAligner.cpp
NullProcessInfo.cpp
PrintOptions.cpp
ProcessInfo.cpp
Pty.cpp
RenameTabDialog.cpp
SSHProcessInfo.cpp
SaveHistoryTask.cpp
Screen.cpp
ScreenWindow.cpp
ScrollState.cpp
SearchHistoryTask.cpp
ShouldApplyProperty.cpp
UnixProcessInfo.cpp
ViewManager.cpp
ViewProperties.cpp
Vt102Emulation.cpp
WindowSystemInfo.cpp
ZModemDialog.cpp
filterHotSpots/EscapeSequenceUrlFilter.cpp
filterHotSpots/EscapeSequenceUrlFilterHotSpot.cpp
filterHotSpots/FileFilter.cpp
filterHotSpots/FileFilterHotspot.cpp
filterHotSpots/Filter.cpp
filterHotSpots/FilterChain.cpp
filterHotSpots/HotSpot.cpp
filterHotSpots/RegExpFilter.cpp
filterHotSpots/RegExpFilterHotspot.cpp
filterHotSpots/TerminalImageFilterChain.cpp
filterHotSpots/UrlFilter.cpp
filterHotSpots/UrlFilterHotspot.cpp
filterHotSpots/ColorFilter.cpp
filterHotSpots/ColorFilterHotSpot.cpp
history/HistoryFile.cpp
history/HistoryScroll.cpp
history/HistoryScrollFile.cpp
history/HistoryScrollNone.cpp
history/HistoryType.cpp
history/HistoryTypeFile.cpp
history/HistoryTypeNone.cpp
history/compact/CompactHistoryScroll.cpp
history/compact/CompactHistoryType.cpp
widgets/DetachableTabBar.cpp
widgets/EditProfileDialog.cpp
widgets/HistorySizeWidget.cpp
widgets/IncrementalSearchBar.cpp
widgets/RenameTabWidget.cpp
widgets/TabTitleFormatButton.cpp
terminalDisplay/extras/CompositeWidgetFocusWatcher.cpp
terminalDisplay/extras/AutoScrollHandler.cpp
terminalDisplay/extras/HighlightScrolledLines.cpp
terminalDisplay/TerminalDisplay.cpp
terminalDisplay/TerminalPainter.cpp
terminalDisplay/TerminalScrollBar.cpp
terminalDisplay/TerminalColor.cpp
terminalDisplay/TerminalFonts.cpp
terminalDisplay/TerminalBell.cpp
profile/Profile.cpp
profile/ProfileCommandParser.cpp
profile/ProfileGroup.cpp
profile/ProfileList.cpp
profile/ProfileReader.cpp
profile/ProfileWriter.cpp
profile/ProfileManager.cpp
profile/ProfileModel.cpp
${ptyqt_srcs} # windows conpty backend
${sessionadaptors_SRCS}
session/Session.cpp
session/SessionController.cpp
session/SessionDisplayConnection.cpp
session/SessionGroup.cpp
session/SessionListModel.cpp
session/SessionManager.cpp
session/SessionTask.cpp
widgets/TerminalDisplayAccessible.cpp
widgets/TerminalHeaderBar.cpp
widgets/ViewContainer.cpp
widgets/ViewSplitter.cpp
widgets/KonsolePrintManager.cpp
${dbus_xml_srcs}
../data/data.qrc
../desktop/konsole.qrc)
kconfig_add_kcfg_files(konsoleprivate_SRCS settings/KonsoleSettings.kcfgc)
### Konsole Application
ki18n_wrap_ui(konsoleprivate_SRCS
CopyInputDialog.ui
HistorySizeDialog.ui
KeyBindingEditor.ui
PrintOptions.ui # Temporary to build session static library
RenameTabDialog.ui
settings/GeneralSettings.ui
settings/MemorySettings.ui
settings/PartInfo.ui
settings/ProfileSettings.ui
settings/TabBarSettings.ui
settings/TemporaryFilesSettings.ui
settings/ThumbnailsSettings.ui
widgets/EditProfileAdvancedPage.ui
widgets/EditProfileAppearancePage.ui
widgets/EditProfileGeneralPage.ui
widgets/EditProfileKeyboardPage.ui
widgets/EditProfileMousePage.ui
widgets/EditProfileScrollingPage.ui
widgets/EditProfileTabsPage.ui
widgets/HistorySizeWidget.ui
widgets/RenameTabWidget.ui
)
add_library(konsoleprivate ${konsoleprivate_SRCS})
generate_export_header(konsoleprivate BASE_NAME konsoleprivate)
find_package(ZLIB)
target_link_libraries(konsoleprivate
PUBLIC
konsoleprivate_core
konsolecolorscheme
keyboardtranslator
konsolehelpers
konsolecharacters
konsoledecoders
KF6::NewStuffCore
KF6::NewStuffWidgets
${konsole_LIBS}
ZLIB::ZLIB
ICU::uc
ICU::i18n
)
target_link_libraries(konsoleprivate
PUBLIC
Qt6::Core5Compat
PRIVATE
KF6::IconWidgets
KF6::BookmarksWidgets
)
set_target_properties(konsoleprivate PROPERTIES
VERSION ${RELEASE_SERVICE_VERSION}
)
install(TARGETS konsoleprivate ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP)
add_library(konsoleapp SHARED Application.cpp
MainWindow.cpp
settings/ConfigurationDialog.cpp
settings/ConfigDialogButtonGroupManager.cpp
settings/TemporaryFilesSettings.cpp
settings/GeneralSettings.cpp
settings/MemorySettings.cpp
settings/ProfileSettings.cpp
settings/TabBarSettings.cpp
settings/ThumbnailsSettings.cpp
pluginsystem/IKonsolePlugin.cpp
pluginsystem/PluginManager.cpp
delegates/ProfileShortcutDelegate.cpp
)
generate_export_header(konsoleapp BASE_NAME konsoleapp)
target_compile_definitions(konsoleapp PRIVATE -DRELEASE_SERVICE_VERSION="${RELEASE_SERVICE_VERSION}")
target_link_libraries(konsoleapp
konsoleprivate
KF6::XmlGui
KF6::WindowSystem
KF6::Bookmarks
KF6::I18n
KF6::KIOWidgets
KF6::NotifyConfig
KF6::Crash
)
set_target_properties(konsoleapp PROPERTIES
VERSION ${RELEASE_SERVICE_VERSION}
)
install(TARGETS konsoleapp ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP)
set(konsole_SRCS
main.cpp
)
# Sets the icon on Windows and OSX
ecm_add_app_icon(ICONS_SOURCES ICONS ${ICONS_SRCS})
add_executable(konsole ${konsole_SRCS} ${ICONS_SOURCES})
target_link_libraries(konsole
konsoleprivate
konsoleapp
KF6::XmlGui
KF6::WindowSystem
KF6::Bookmarks
KF6::I18n
KF6::KIOWidgets
KF6::NotifyConfig
KF6::Crash
)
if(APPLE)
set_target_properties(konsole PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.konsole"
MACOSX_BUNDLE_BUNDLE_NAME "Konsole"
MACOSX_BUNDLE_DISPLAY_NAME "Konsole"
MACOSX_BUNDLE_INFO_STRING "Konsole, the KDE terminal emulator"
MACOSX_BUNDLE_LONG_VERSION_STRING "Konsole ${RELEASE_SERVICE_VERSION}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}"
MACOSX_BUNDLE_BUNDLE_VERSION "${RELEASE_SERVICE_VERSION}"
MACOSX_BUNDLE_COPYRIGHT "1997-2022 The Konsole Developers")
endif()
install(TARGETS konsole ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
### Embedded Konsole KPart
set(konsolepart_PART_SRCS Part.cpp
settings/PartInfo.cpp
settings/ProfileSettings.cpp
delegates/ProfileShortcutDelegate.cpp
)
add_library(konsolepart MODULE ${konsolepart_PART_SRCS})
generate_export_header(konsolepart BASE_NAME konsole)
set_target_properties(konsolepart PROPERTIES DEFINE_SYMBOL KONSOLE_PART)
target_link_libraries(konsolepart
KF6::Parts
KF6::XmlGui
konsoleprivate
)
install(TARGETS konsolepart DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf6/parts)
# Add cmake-options -DBUILD_COVERAGE=ON -DCOVERAGE_GENERATE_HTML=ON
if(COVERAGE_GENERATE_HTML)
# find required tools - no error checking
find_program(LCOV lcov REQUIRED)
find_program(GENHTML genhtml REQUIRED)
add_custom_target(coverage
# runs tests to get .gcda files
COMMAND ctest --progress
# generate data
COMMAND ${LCOV} --directory . --capture --output-file coverage.info
# generate report
COMMAND ${GENHTML} --ignore-errors source --demangle-cpp -o coverage coverage.info
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
endif()
@@ -0,0 +1,97 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "CheckableSessionModel.h"
using namespace Konsole;
CheckableSessionModel::CheckableSessionModel(QObject *parent)
: SessionListModel(parent)
, _checkedSessions(QSet<Session *>())
, _fixedSessions(QSet<Session *>())
, _checkColumn(0)
{
}
void CheckableSessionModel::setCheckColumn(int column)
{
beginResetModel();
_checkColumn = column;
endResetModel();
}
Qt::ItemFlags CheckableSessionModel::flags(const QModelIndex &index) const
{
auto *session = static_cast<Session *>(index.internalPointer());
if (_fixedSessions.contains(session)) {
return SessionListModel::flags(index) & ~Qt::ItemIsEnabled;
}
return SessionListModel::flags(index) | Qt::ItemIsUserCheckable;
}
QVariant CheckableSessionModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::CheckStateRole && index.column() == _checkColumn) {
auto *session = static_cast<Session *>(index.internalPointer());
return QVariant::fromValue(static_cast<int>(_checkedSessions.contains(session) ? Qt::Checked : Qt::Unchecked));
}
return SessionListModel::data(index, role);
}
bool CheckableSessionModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::CheckStateRole && index.column() == _checkColumn) {
auto *session = static_cast<Session *>(index.internalPointer());
if (_fixedSessions.contains(session)) {
return false;
}
if (value.toInt() == Qt::Checked) {
_checkedSessions.insert(session);
} else {
_checkedSessions.remove(session);
}
Q_EMIT dataChanged(index, index);
return true;
}
return SessionListModel::setData(index, value, role);
}
void CheckableSessionModel::setCheckedSessions(const QSet<Session *> &sessions)
{
beginResetModel();
_checkedSessions = sessions;
endResetModel();
}
QSet<Session *> CheckableSessionModel::checkedSessions() const
{
return _checkedSessions;
}
void CheckableSessionModel::setCheckable(Session *session, bool checkable)
{
beginResetModel();
if (!checkable) {
_fixedSessions.insert(session);
} else {
_fixedSessions.remove(session);
}
endResetModel();
}
void CheckableSessionModel::sessionRemoved(Session *session)
{
_checkedSessions.remove(session);
_fixedSessions.remove(session);
}
#include "moc_CheckableSessionModel.cpp"
@@ -0,0 +1,66 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef CHECKABLESESSIONMODEL_H
#define CHECKABLESESSIONMODEL_H
// Qt
#include <QSet>
// Konsole
#include "session/Session.h"
#include "session/SessionListModel.h"
namespace Konsole
{
/**
* A list of sessions with a checkbox next to each one which allows the
* user to select a subset of the available sessions to perform
* some action on them.
*/
class CheckableSessionModel : public SessionListModel
{
Q_OBJECT
public:
explicit CheckableSessionModel(QObject *parent);
void setCheckColumn(int column);
int checkColumn() const;
/**
* Sets whether a session can be checked or un-checked.
* Non-checkable items have the Qt::ItemIsEnabled flag unset.
*/
void setCheckable(Session *session, bool checkable);
/** Sets the list of sessions which are currently checked. */
void setCheckedSessions(const QSet<Session *> &sessions);
/** Returns the set of checked sessions. */
QSet<Session *> checkedSessions() const;
// reimplemented from QAbstractItemModel
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
protected:
void sessionRemoved(Session *) override;
private:
QSet<Session *> _checkedSessions;
QSet<Session *> _fixedSessions;
int _checkColumn;
};
inline int CheckableSessionModel::checkColumn() const
{
return _checkColumn;
}
}
#endif
@@ -0,0 +1,129 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "CopyInputDialog.h"
// Qt
#include <QSortFilterProxyModel>
// Konsole
#include "CheckableSessionModel.h"
#include "ui_CopyInputDialog.h"
#include <KLocalizedString>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
using namespace Konsole;
CopyInputDialog::CopyInputDialog(QWidget *parent)
: QDialog(parent)
, _ui(nullptr)
, _model(nullptr)
, _masterSession(nullptr)
{
setWindowTitle(i18n("Copy Input"));
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto mainWidget = new QWidget(this);
auto mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
connect(buttonBox, &QDialogButtonBox::accepted, this, &CopyInputDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &CopyInputDialog::reject);
mainLayout->addWidget(buttonBox);
setWindowModality(Qt::WindowModal);
_ui = new Ui::CopyInputDialog();
_ui->setupUi(mainWidget);
connect(_ui->selectAllButton, &QPushButton::clicked, this, &Konsole::CopyInputDialog::selectAll);
connect(_ui->deselectAllButton, &QPushButton::clicked, this, &Konsole::CopyInputDialog::deselectAll);
_ui->filterEdit->setClearButtonEnabled(true);
_ui->filterEdit->setFocus();
_model = new CheckableSessionModel(parent);
_model->setCheckColumn(1);
_model->setSessions(SessionManager::instance()->sessions());
auto filterProxyModel = new QSortFilterProxyModel(this);
filterProxyModel->setDynamicSortFilter(true);
filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
filterProxyModel->setSourceModel(_model);
filterProxyModel->setFilterKeyColumn(-1);
connect(_ui->filterEdit, &QLineEdit::textChanged, filterProxyModel, &QSortFilterProxyModel::setFilterFixedString);
_ui->sessionList->setModel(filterProxyModel);
_ui->sessionList->setColumnHidden(0, true); // Hide number column
_ui->sessionList->header()->hide();
}
CopyInputDialog::~CopyInputDialog()
{
delete _ui;
}
void CopyInputDialog::setChosenSessions(const QSet<Session *> &sessions)
{
QSet<Session *> checked = sessions;
if (!_masterSession.isNull()) {
checked.insert(_masterSession);
}
_model->setCheckedSessions(checked);
}
QSet<Session *> CopyInputDialog::chosenSessions() const
{
return _model->checkedSessions();
}
void CopyInputDialog::setMasterSession(Session *session)
{
if (!_masterSession.isNull()) {
_model->setCheckable(_masterSession, true);
}
_model->setCheckable(session, false);
QSet<Session *> checked = _model->checkedSessions();
checked.insert(session);
_model->setCheckedSessions(checked);
_masterSession = session;
}
void CopyInputDialog::setSelectionChecked(bool checked)
{
QAbstractItemModel *model = _ui->sessionList->model();
int rows = model->rowCount();
const QModelIndexList selected = _ui->sessionList->selectionModel()->selectedIndexes();
if (selected.count() > 1) {
for (const QModelIndex &index : selected) {
setRowChecked(index.row(), checked);
}
} else {
for (int i = 0; i < rows; i++) {
setRowChecked(i, checked);
}
}
}
void CopyInputDialog::setRowChecked(int row, bool checked)
{
QAbstractItemModel *model = _ui->sessionList->model();
QModelIndex index = model->index(row, _model->checkColumn());
model->setData(index, static_cast<int>(checked ? Qt::Checked : Qt::Unchecked), Qt::CheckStateRole);
}
#include "moc_CopyInputDialog.cpp"
@@ -0,0 +1,83 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef COPYINPUTDIALOG_H
#define COPYINPUTDIALOG_H
// Qt
#include <QPointer>
#include <QSet>
// KDE
#include <QDialog>
// Konsole
#include "konsoleprivate_export.h"
#include "session/Session.h"
#include "session/SessionListModel.h"
#include "session/SessionManager.h"
namespace Ui
{
class CopyInputDialog;
}
namespace Konsole
{
class CheckableSessionModel;
/**
* Dialog which allows the user to mark a list of sessions to copy
* the input from the current session to. The current session is
* set using setMasterSession(). After the dialog has been executed,
* the set of chosen sessions can be retrieved using chosenSessions()
*/
class KONSOLEPRIVATE_EXPORT CopyInputDialog : public QDialog
{
Q_OBJECT
public:
explicit CopyInputDialog(QWidget *parent = nullptr);
~CopyInputDialog() override;
/**
* Sets the 'source' session whose input will be copied to
* other sessions. This session is displayed grayed out in the list
* and cannot be unchecked.
*/
void setMasterSession(Session *session);
/** Sets the sessions in the list which are checked. */
void setChosenSessions(const QSet<Session *> &sessions);
/** Set setChosenSessions() */
QSet<Session *> chosenSessions() const;
private Q_SLOTS:
void selectAll()
{
setSelectionChecked(true);
}
void deselectAll()
{
setSelectionChecked(false);
}
private:
Q_DISABLE_COPY(CopyInputDialog)
// Checks or unchecks selected sessions. If there are no
// selected items then all sessions are checked or unchecked
void setSelectionChecked(bool checked);
void setRowChecked(int row, bool checked);
Ui::CopyInputDialog *_ui;
CheckableSessionModel *_model;
QPointer<Session> _masterSession;
};
}
#endif // COPYINPUTDIALOG_H
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CopyInputDialog</class>
<widget class="QWidget" name="CopyInputDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>363</width>
<height>223</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Filter:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLineEdit" name="filterEdit"/>
</item>
<item>
<widget class="QTreeView" name="sessionList">
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::ExtendedSelection</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="selectAllButton">
<property name="text">
<string>Select All</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deselectAllButton">
<property name="text">
<string>Deselect All</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,334 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
SPDX-FileCopyrightText: 1996 Matthias Ettrich <ettrich@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "Emulation.h"
// Qt
#include <QKeyEvent>
// Konsole
#include "Screen.h"
#include "ScreenWindow.h"
#include "keyboardtranslator/KeyboardTranslator.h"
#include "keyboardtranslator/KeyboardTranslatorManager.h"
using namespace Konsole;
Emulation::Emulation()
{
// create screens with a default size
_screen[0] = new Screen(40, 80);
_screen[1] = new Screen(40, 80);
_currentScreen = _screen[0];
QObject::connect(&_bulkTimer1, &QTimer::timeout, this, &Konsole::Emulation::showBulk);
QObject::connect(&_bulkTimer2, &QTimer::timeout, this, &Konsole::Emulation::showBulk);
// listen for mouse status changes
connect(this, &Konsole::Emulation::programRequestsMouseTracking, this, &Konsole::Emulation::setUsesMouseTracking);
connect(this, &Konsole::Emulation::programBracketedPasteModeChanged, this, &Konsole::Emulation::bracketedPasteModeChanged);
}
bool Emulation::programUsesMouseTracking() const
{
return _usesMouseTracking;
}
void Emulation::setUsesMouseTracking(bool usesMouseTracking)
{
_usesMouseTracking = usesMouseTracking;
}
bool Emulation::programBracketedPasteMode() const
{
return _bracketedPasteMode;
}
void Emulation::bracketedPasteModeChanged(bool bracketedPasteMode)
{
_bracketedPasteMode = bracketedPasteMode;
}
ScreenWindow *Emulation::createWindow()
{
auto window = new ScreenWindow(_currentScreen);
_windows << window;
connect(window, &Konsole::ScreenWindow::selectionChanged, this, &Konsole::Emulation::bufferedUpdate);
connect(window, &Konsole::ScreenWindow::selectionChanged, this, &Konsole::Emulation::checkSelectedText);
connect(this, &Konsole::Emulation::outputChanged, window, &Konsole::ScreenWindow::notifyOutputChanged);
return window;
}
void Emulation::setCurrentTerminalDisplay(TerminalDisplay *display)
{
_screen[0]->setCurrentTerminalDisplay(display);
_screen[1]->setCurrentTerminalDisplay(display);
}
void Emulation::checkScreenInUse()
{
Q_EMIT primaryScreenInUse(_currentScreen == _screen[0]);
}
void Emulation::checkSelectedText()
{
bool isEmpty = !_currentScreen->hasSelection();
Q_EMIT selectionChanged(isEmpty);
}
Emulation::~Emulation()
{
for (ScreenWindow *window : std::as_const(_windows)) {
delete window;
}
delete _screen[0];
delete _screen[1];
}
void Emulation::setPeekPrimary(const bool doPeek)
{
if (doPeek == _peekingPrimary) {
return;
}
_peekingPrimary = doPeek;
setScreenInternal(doPeek ? 0 : _activeScreenIndex);
Q_EMIT outputChanged();
}
void Emulation::setScreen(int index)
{
_activeScreenIndex = index;
_peekingPrimary = false;
setScreenInternal(_activeScreenIndex);
}
void Emulation::setScreenInternal(int index)
{
Screen *oldScreen = _currentScreen;
_currentScreen = _screen[index & 1];
if (_currentScreen != oldScreen) {
// tell all windows onto this emulation to switch to the newly active screen
for (ScreenWindow *window : std::as_const(_windows)) {
window->setScreen(_currentScreen);
}
checkScreenInUse();
checkSelectedText();
}
}
void Emulation::clearHistory()
{
_screen[0]->setScroll(_screen[0]->getScroll(), false);
}
void Emulation::setHistory(const HistoryType &history)
{
_screen[0]->setScroll(history);
showBulk();
}
const HistoryType &Emulation::history() const
{
return _screen[0]->getScroll();
}
void Emulation::setCodec(const QTextCodec *codec)
{
if (codec != nullptr) {
_codec = codec;
_decoder.reset(_codec->makeDecoder());
Q_EMIT useUtf8Request(utf8());
} else {
#if defined(Q_OS_WIN)
setCodec(Utf8Codec);
#else
setCodec(LocaleCodec);
#endif
}
}
void Emulation::setCodec(EmulationCodec codec)
{
if (codec == Utf8Codec) {
setCodec(QTextCodec::codecForName("utf8"));
} else if (codec == LocaleCodec) {
setCodec(QTextCodec::codecForLocale());
}
}
void Emulation::setKeyBindings(const QString &name)
{
_keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name);
if (_keyTranslator == nullptr) {
_keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator();
}
}
QString Emulation::keyBindings() const
{
return _keyTranslator->name();
}
// process application unicode input to terminal
// this is a trivial scanner
void Emulation::receiveChars(const QVector<uint> &chars)
{
for (uint c : chars) {
c &= 0xff;
switch (c) {
case '\b':
_currentScreen->backspace();
break;
case '\t':
_currentScreen->tab();
break;
case '\n':
_currentScreen->newLine();
break;
case '\r':
_currentScreen->toStartOfLine();
break;
case 0x07:
Q_EMIT bell();
break;
default:
_currentScreen->displayCharacter(c);
break;
}
}
}
void Emulation::sendKeyEvent(QKeyEvent *ev)
{
if (!ev->text().isEmpty()) {
// A block of text
// Note that the text is proper unicode.
// We should do a conversion here
Q_EMIT sendData(ev->text().toLocal8Bit());
}
}
void Emulation::receiveData(const char *text, int length)
{
Q_ASSERT(_decoder);
bufferedUpdate();
// send characters to terminal emulator
const QVector<uint> chars = _decoder->toUnicode(text, length).toUcs4();
receiveChars(chars);
// look for z-modem indicator
//-- someone who understands more about z-modems that I do may be able to move
// this check into the above for loop?
auto *found = static_cast<const char *>(memchr(text, '\030', length));
if (found) {
auto startPos = found - text;
if (startPos < 0) {
return;
}
for (int i = startPos; i < length - 4; i++) {
if (text[i] == '\030') {
if (qstrncmp(text + i + 1, "B00", 3) == 0) {
Q_EMIT zmodemDownloadDetected();
} else if (qstrncmp(text + i + 1, "B01", 3) == 0) {
Q_EMIT zmodemUploadDetected();
}
}
}
}
}
void Emulation::writeToStream(TerminalCharacterDecoder *decoder, int startLine, int endLine)
{
_currentScreen->writeLinesToStream(decoder, startLine, endLine);
}
int Emulation::lineCount() const
{
// sum number of lines currently on _screen plus number of lines in history
return _currentScreen->getLines() + _currentScreen->getHistLines();
}
void Emulation::showBulk()
{
_bulkTimer1.stop();
_bulkTimer2.stop();
Q_EMIT outputChanged();
_currentScreen->resetScrolledLines();
_currentScreen->resetDroppedLines();
}
void Emulation::bufferedUpdate()
{
static const int BULK_TIMEOUT1 = 10;
static const int BULK_TIMEOUT2 = 40;
_bulkTimer1.setSingleShot(true);
_bulkTimer1.start(BULK_TIMEOUT1);
if (!_bulkTimer2.isActive()) {
_bulkTimer2.setSingleShot(true);
_bulkTimer2.start(BULK_TIMEOUT2);
}
}
char Emulation::eraseChar() const
{
return '\b';
}
void Emulation::setImageSize(int lines, int columns)
{
if ((lines < 1) || (columns < 1)) {
return;
}
QSize screenSize[2] = {QSize(_screen[0]->getColumns(), _screen[0]->getLines()), //
QSize(_screen[1]->getColumns(), _screen[1]->getLines())};
QSize newSize(columns, lines);
if (newSize == screenSize[0] && newSize == screenSize[1]) {
// If this method is called for the first time, always emit
// imageSizeChanged() signal, even if the new size is the same as the
// current size. See #176902
if (!_imageSizeInitialized) {
Q_EMIT imageSizeChanged(lines, columns);
}
} else {
_screen[0]->resizeImage(lines, columns);
_screen[1]->resizeImage(lines, columns);
Q_EMIT imageSizeChanged(lines, columns);
bufferedUpdate();
}
if (!_imageSizeInitialized) {
_imageSizeInitialized = true;
Q_EMIT imageSizeInitialized();
}
}
QSize Emulation::imageSize() const
{
return {_currentScreen->getColumns(), _currentScreen->getLines()};
}
#include "moc_Emulation.cpp"
@@ -0,0 +1,489 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef EMULATION_H
#define EMULATION_H
// Qt
#include <QSize>
#include <QTextCodec>
#include <QTimer>
// Konsole
#include "Enumeration.h"
#include "konsoleprivate_export.h"
#include "terminalDisplay/TerminalDisplay.h"
#include <memory>
class QKeyEvent;
namespace Konsole
{
class KeyboardTranslator;
class HistoryType;
class Screen;
class ScreenWindow;
class TerminalCharacterDecoder;
/**
* Base class for terminal emulation back-ends.
*
* The back-end is responsible for decoding an incoming character stream and
* producing an output image of characters.
*
* When input from the terminal is received, the receiveData() slot should be called with
* the data which has arrived. The emulation will process the data and update the
* screen image accordingly. The codec used to decode the incoming character stream
* into the unicode characters used internally can be specified using setCodec()
*
* The size of the screen image can be specified by calling setImageSize() with the
* desired number of lines and columns. When new lines are added, old content
* is moved into a history store, which can be set by calling setHistory().
*
* The screen image can be accessed by creating a ScreenWindow onto this emulation
* by calling createWindow(). Screen windows provide access to a section of the
* output. Each screen window covers the same number of lines and columns as the
* image size returned by imageSize(). The screen window can be moved up and down
* and provides transparent access to both the current on-screen image and the
* previous output. The screen windows emit an outputChanged signal
* when the section of the image they are looking at changes.
* Graphical views can then render the contents of a screen window, listening for notifications
* of output changes from the screen window which they are associated with and updating
* accordingly.
*
* The emulation also is also responsible for converting input from the connected views such
* as keypresses and mouse activity into a character string which can be sent
* to the terminal program. Key presses can be processed by calling the sendKeyEvent() slot,
* while mouse events can be processed using the sendMouseEvent() slot. When the character
* stream has been produced, the emulation will emit a sendData() signal with a pointer
* to the character buffer. This data should be fed to the standard input of the terminal
* process. The translation of key presses into an output character stream is performed
* using a lookup in a set of key bindings which map key sequences to output
* character sequences. The name of the key bindings set used can be specified using
* setKeyBindings()
*
* The emulation maintains certain state information which changes depending on the
* input received. The emulation can be reset back to its starting state by calling
* reset().
*
* The emulation also maintains an activity state, which specifies whether
* terminal is currently active ( when data is received ), normal
* ( when the terminal is idle or receiving user input ) or trying
* to alert the user ( also known as a "Bell" event ). The stateSet() signal
* is emitted whenever the activity state is set. This can be used to determine
* how long the emulation has been active/idle for and also respond to
* a 'bell' event in different ways.
*/
class KONSOLEPRIVATE_EXPORT Emulation : public QObject
{
Q_OBJECT
public:
/** Constructs a new terminal emulation */
Emulation();
~Emulation() override;
/**
* Creates a new window onto the output from this emulation. The contents
* of the window are then rendered by views which are set to use this window using the
* TerminalDisplay::setScreenWindow() method.
*/
ScreenWindow *createWindow();
/**
* Associates a TerminalDisplay with this emulation.
*/
void setCurrentTerminalDisplay(TerminalDisplay *display);
/** Returns the size of the screen image which the emulation produces */
QSize imageSize() const;
/**
* Returns the total number of lines, including those stored in the history.
*/
int lineCount() const;
/**
* Sets the history store used by this emulation. When new lines
* are added to the output, older lines at the top of the screen are transferred to a history
* store.
*
* The number of lines which are kept and the storage location depend on the
* type of store.
*/
void setHistory(const HistoryType &);
/** Returns the history store used by this emulation. See setHistory() */
const HistoryType &history() const;
/** Clears the history scroll. */
virtual void clearHistory();
/**
* Copies the output history from @p startLine to @p endLine
* into @p stream, using @p decoder to convert the terminal
* characters into text.
*
* @param decoder A decoder which converts lines of terminal characters with
* appearance attributes into output text. PlainTextDecoder is the most commonly
* used decoder.
* @param startLine Index of first line to copy
* @param endLine Index of last line to copy
*/
virtual void writeToStream(TerminalCharacterDecoder *decoder, int startLine, int endLine);
/** Returns the codec used to decode incoming characters. See setCodec() */
const QTextCodec *codec() const
{
return _codec;
}
/** Sets the codec used to decode incoming characters. */
void setCodec(const QTextCodec *);
/**
* Convenience method.
* Returns true if the current codec used to decode incoming
* characters is UTF-8
*/
bool utf8() const
{
Q_ASSERT(_codec);
return _codec->mibEnum() == 106;
}
/** Returns the special character used for erasing character. */
virtual char eraseChar() const;
/**
* Sets the key bindings used to key events
* ( received through sendKeyEvent() ) into character
* streams to send to the terminal.
*/
void setKeyBindings(const QString &name);
/**
* Returns the name of the emulation's current key bindings.
* See setKeyBindings()
*/
QString keyBindings() const;
/**
* Copies the current image into the history and clears the screen.
*/
virtual void clearEntireScreen() = 0;
/** Resets the state of the terminal.
*
* @param softReset The reset was initiated by DECSTR
* @param preservePrompt Try to preserve the command prompt */
virtual void reset(bool softReset = false, bool preservePrompt = false) = 0;
/**
* Returns true if the active terminal program is interested in Mouse
* Tracking events.
*
* The programRequestsMouseTracking() signal is emitted when a program
* indicates it's interested in Mouse Tracking events.
*
* See MODE_Mouse100{0,1,2,3} in Vt102Emulation.
*/
bool programUsesMouseTracking() const;
bool programBracketedPasteMode() const;
public Q_SLOTS:
/** Change the size of the emulation's image */
virtual void setImageSize(int lines, int columns);
/**
* Interprets a sequence of characters and sends the result to the terminal.
* This is equivalent to calling sendKeyEvent() for each character in @p text in succession.
*/
virtual void sendText(const QString &text) = 0;
/**
* Interprets a key press event and emits the sendData() signal with
* the resulting character stream.
*/
virtual void sendKeyEvent(QKeyEvent *);
/**
* Converts information about a mouse event into an xterm-compatible escape
* sequence and emits the character sequence via sendData()
*/
virtual void sendMouseEvent(int buttons, int column, int line, int eventType) = 0;
/**
* Sends a string of characters to the foreground terminal process.
*
* @param string The characters to send.
*/
virtual void sendString(const QByteArray &string) = 0;
/**
* Processes an incoming stream of characters. receiveData() decodes the incoming
* character buffer using the current codec(), and then calls receiveChar() for
* each unicode character in the resulting buffer.
*
* receiveData() also starts a timer which causes the outputChanged() signal
* to be emitted when it expires. The timer allows multiple updates in quick
* succession to be buffered into a single outputChanged() signal emission.
*
* @param text A string of characters received from the terminal program.
* @param length The length of @p text
*/
void receiveData(const char *text, int length);
/**
* Sends information about the focus event to the terminal.
*/
virtual void focusChanged(bool focused) = 0;
void setPeekPrimary(const bool doPeek);
Q_SIGNALS:
/**
* Emitted when a buffer of data is ready to send to the
* standard input of the terminal.
*
* @param data The buffer of data ready to be sent
*/
void sendData(const QByteArray &data);
/**
* Requests that the pty used by the terminal process
* be set to UTF 8 mode.
*
* Refer to the IUTF8 entry in termios(3) for more information.
*/
void useUtf8Request(bool);
/**
* Emitted when bell appeared
*/
void bell();
/**
* Emitted when the special sequence indicating the request for data
* transmission through ZModem protocol is detected.
*/
void zmodemDownloadDetected();
void zmodemUploadDetected();
/**
* This is emitted when the program (typically editors and other full-screen
* applications, ones that take up the whole terminal display), running in
* the terminal indicates whether or not it is interested in Mouse Tracking
* events. This is an XTerm extension, for more information have a look at:
* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
*
* @param usesMouseTracking This will be true if the program is interested
* in Mouse Tracking events or false otherwise.
*/
void programRequestsMouseTracking(bool usesMouseTracking);
void enableAlternateScrolling(bool enable);
void programBracketedPasteModeChanged(bool bracketedPasteMode);
/**
* Emitted when the contents of the screen image change.
* The emulation buffers the updates from successive image changes,
* and only emits outputChanged() at sensible intervals when
* there is a lot of terminal activity.
*
* Normally there is no need for objects other than the screen windows
* created with createWindow() to listen for this signal.
*
* ScreenWindow objects created using createWindow() will emit their
* own outputChanged() signal in response to this signal.
*/
void outputChanged();
/**
* Emitted when the program running in the terminal wishes to update
* certain session attributes. This allows terminal programs to customize
* certain aspects of the terminal emulation display.
*
* This signal is emitted when the escape sequence "\033]ARG;VALUE\007"
* is received in an input string, where ARG is a number specifying
* what should be changed and VALUE is a string specifying the new value.
*
* @param attribute Specifies which session attribute to change:
* <ul>
* <li> 0 - Set window icon text and session title to @p newValue</li>
* <li> 1 - Set window icon text to @p newValue</li>
* <li> 2 - Set session title to @p newValue</li>
* <li> 11 - Set the session's default background color to @p newValue,
* where @p newValue can be an HTML-style string ("#RRGGBB") or a
* named color (e.g. 'red', 'blue'). For more details see:
* https://doc.qt.io/qt-5/qcolor.html#setNamedColor
* </li>
* <li> 31 - Supposedly treats @p newValue as a URL and opens it (NOT
* IMPLEMENTED)
* </li>
* <li> 32 - Sets the icon associated with the session. @p newValue
* is the name of the icon to use, which can be the name of any
* icon in the current KDE icon theme (eg: 'konsole', 'kate',
* 'folder_home')
* </li>
* </ul>
* @param newValue Specifies the new value of the session attribute
*/
void sessionAttributeChanged(int attribute, const QString &newValue);
/**
* Emitted when the terminal emulator's size has changed
*/
void imageSizeChanged(int lineCount, int columnCount);
/**
* Emitted when the setImageSize() is called on this emulation for
* the first time.
*/
void imageSizeInitialized();
/**
* Emitted after receiving the escape sequence which asks to change
* the terminal emulator's size
*/
void imageResizeRequest(const QSize &sizz);
/**
* Emitted when the terminal program requests to change various properties
* of the terminal display.
*
* A profile change command occurs when a special escape sequence, followed
* by a string containing a series of name and value pairs is received.
* This string can be parsed using a ProfileCommandParser instance.
*
* @param text A string expected to contain a series of key and value pairs in
* the form: name=value;name2=value2 ...
*/
void profileChangeCommandReceived(const QString &text);
/**
* Emitted when a flow control key combination ( Ctrl+S or Ctrl+Q ) is pressed.
* @param suspendKeyPressed True if Ctrl+S was pressed to suspend output or Ctrl+Q to
* resume output.
*/
void flowControlKeyPressed(bool suspendKeyPressed);
/**
* Emitted when the active screen is switched, to indicate whether the primary
* screen is in use.
*/
void primaryScreenInUse(bool use);
/**
* Emitted when the text selection is changed
*/
void selectionChanged(const bool selectionEmpty);
/**
* Emitted when terminal code requiring terminal's response received.
*/
void sessionAttributeRequest(int id, uint terminator);
/**
* Emitted when Set Cursor Style (DECSCUSR) escape sequences are sent
* to the terminal.
* @p shape cursor shape
* @p isBlinking if true, the cursor will be set to blink
* @p customColor custom cursor color
*/
void setCursorStyleRequest(Enum::CursorShapeEnum shape = Enum::BlockCursor, bool isBlinking = false, const QColor &customColor = {});
/**
* Emitted when reset() is called to reset the cursor style to the
* current profile cursor shape and blinking settings.
*/
void resetCursorStyleRequest();
void toggleUrlExtractionRequest();
protected:
virtual void setMode(int mode) = 0;
virtual void resetMode(int mode) = 0;
/**
* Processes an incoming character. See receiveData()
* @p c A unicode character code.
*/
virtual void receiveChars(const QVector<uint> &c);
/**
* Sets the active screen. The terminal has two screens, primary and alternate.
* The primary screen is used by default. When certain interactive programs such
* as Vim are run, they trigger a switch to the alternate screen.
*
* @param index 0 to switch to the primary screen, or 1 to switch to the alternate screen
*/
void setScreen(int index);
enum EmulationCodec {
LocaleCodec = 0,
Utf8Codec = 1,
};
void setCodec(EmulationCodec codec);
QList<ScreenWindow *> _windows;
Screen *_currentScreen = nullptr; // pointer to the screen which is currently active,
// this is one of the elements in the screen[] array
Screen *_screen[2]; // 0 = primary screen ( used by most programs, including the shell
// scrollbars are enabled in this mode )
// 1 = alternate ( used by vi , emacs etc.
// scrollbars are not enabled in this mode )
// decodes an incoming C-style character stream into a unicode QString using
// the current text codec. (this allows for rendering of non-ASCII characters in text files etc.)
const QTextCodec *_codec = nullptr;
std::unique_ptr<QTextDecoder> _decoder;
const KeyboardTranslator *_keyTranslator = nullptr; // the keyboard layout
protected Q_SLOTS:
/**
* Schedules an update of attached views.
* Repeated calls to bufferedUpdate() in close succession will result in only a single update,
* much like the Qt buffered update of widgets.
*/
void bufferedUpdate();
// used to emit the primaryScreenInUse(bool) signal
void checkScreenInUse();
// used to emit the selectionChanged(QString) signal
void checkSelectedText();
private Q_SLOTS:
// triggered by timer, causes the emulation to send an updated screen image to each
// view
void showBulk();
void setUsesMouseTracking(bool usesMouseTracking);
void bracketedPasteModeChanged(bool bracketedPasteMode);
private:
void setScreenInternal(int index);
Q_DISABLE_COPY(Emulation)
bool _usesMouseTracking = false;
bool _bracketedPasteMode = false;
QTimer _bulkTimer1{this};
QTimer _bulkTimer2{this};
bool _imageSizeInitialized = false;
bool _peekingPrimary = false;
int _activeScreenIndex = 0;
};
}
#endif // ifndef EMULATION_H
@@ -0,0 +1,148 @@
/*
SPDX-FileCopyrightText: 2012 Jekyll Wu <adaptee@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef ENUMERATION_H
#define ENUMERATION_H
namespace Konsole
{
/**
* This class serves as a place for putting enum definitions that are
* used or referenced in multiple places in the code. Keep it small.
*/
class Enum
{
public:
/**
* This enum describes the modes available to remember lines of output
* produced by the terminal.
*/
enum HistoryModeEnum {
/** No output is remembered. As soon as lines of text are scrolled
* off-screen they are lost.
*/
NoHistory = 0,
/** A fixed number of lines of output are remembered. Once the
* limit is reached, the oldest lines are lost.
*/
FixedSizeHistory = 1,
/** All output is remembered for the duration of the session.
* Typically this means that lines are recorded to
* a file as they are scrolled off-screen.
*/
UnlimitedHistory = 2,
};
/**
* This enum describes the positions where the terminal display's
* scroll bar may be placed.
*/
enum ScrollBarPositionEnum {
/** Show the scroll-bar on the left of the terminal display. */
ScrollBarLeft = 0,
/** Show the scroll-bar on the right of the terminal display. */
ScrollBarRight = 1,
/** Do not show the scroll-bar. */
ScrollBarHidden = 2,
};
/**
* This enum describes the amount that Page Up/Down scroll by.
*/
enum ScrollPageAmountEnum {
/** Scroll half page */
ScrollPageHalf = 0,
/** Scroll full page */
ScrollPageFull = 1,
};
/** This enum describes semantic hints appearance
*/
enum Hints {
HintsNever = 0,
HintsURL = 1,
HintsAlways = 2,
};
/** This enum describes the shapes used to draw the cursor in terminal
* displays.
*/
enum CursorShapeEnum {
/** Use a solid rectangular block to draw the cursor. */
BlockCursor = 0,
/** Use an 'I' shape, similar to that used in text editing
* applications, to draw the cursor.
*/
IBeamCursor = 1,
/** Draw a line underneath the cursor's position. */
UnderlineCursor = 2,
};
/** This enum describes the behavior of triple click action . */
enum TripleClickModeEnum {
/** Select the whole line underneath the cursor. */
SelectWholeLine = 0,
/** Select from the current cursor position to the end of the line. */
SelectForwardsFromCursor = 1,
};
/** This enum describes the source from which mouse middle click pastes data . */
enum MiddleClickPasteModeEnum {
/** Paste from X11 Selection. */
PasteFromX11Selection = 0,
/** Paste from Clipboard. */
PasteFromClipboard = 1,
};
/**
* This enum describes the text editor cmd used to open local text file URLs
* in Konsole, where line and column data are appended to the file URL, e.g.:
* /path/to/file:123:123
*/
enum TextEditorCmd {
Kate = 0,
KWrite,
KDevelop,
QtCreator,
Gedit,
gVim,
CustomTextEditor,
Default,
};
/**
* This enum describes the different types of sounds and visual effects which
* can be used to alert the user when a 'bell' occurs in the terminal
* session.
*/
enum BellModeEnum {
/** trigger system beep. */
SystemBeepBell = 0,
/** trigger system notification. */
NotifyBell = 1,
/** trigger visual bell(inverting the display's colors briefly). */
VisualBell = 2,
/** No bell effects */
NoBell = 3,
};
/**
* This enum describes the strategies available for searching
* through the session's output.
*/
enum SearchDirection {
/** Searches forwards through the output, starting at the
* current selection.
*/
ForwardsSearch,
/** Searches backwards through the output, starting at the
* current selection.
*/
BackwardsSearch,
};
};
}
#endif // ENUMERATION_H
@@ -0,0 +1,114 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "EscapeSequenceUrlExtractor.h"
#include "Screen.h"
#include <QUrl>
namespace Konsole
{
EscapeSequenceUrlExtractor::EscapeSequenceUrlExtractor() = default;
void Konsole::EscapeSequenceUrlExtractor::setScreen(Konsole::Screen *screen)
{
_screen = screen;
clear();
}
void EscapeSequenceUrlExtractor::beginUrlInput()
{
_reading = true;
}
void EscapeSequenceUrlExtractor::appendUrlText_impl(uint c)
{
if (_currentUrl.text.isEmpty()) {
// We need to on getCursorX because we want the position of the
// last printed character, not the cursor.
const int realCcolumn = _screen->getCursorY() + _screen->getHistLines();
_currentUrl.begin = Coordinate{realCcolumn, _screen->getCursorX() - 1};
}
_currentUrl.text += QString::fromUcs4(&c, 1);
}
void EscapeSequenceUrlExtractor::setUrl(const QString &url)
{
if (_allowedUriSchemas.contains(QUrl(url).scheme() + QLatin1String("://"))) {
_currentUrl.url = url;
} else {
abortUrlInput();
}
}
void EscapeSequenceUrlExtractor::abortUrlInput()
{
_reading = false;
_currentUrl = ExtractedUrl{};
_ignoreNextUrlInput = true;
}
void EscapeSequenceUrlExtractor::endUrlInput()
{
Q_ASSERT(reading());
_reading = false;
const int realCcolumn = _screen->getCursorY() + _screen->getHistLines();
const auto currentPos = Coordinate{realCcolumn, _screen->getCursorX()};
_currentUrl.end = currentPos;
_history.append(_currentUrl);
_currentUrl = ExtractedUrl{};
}
void EscapeSequenceUrlExtractor::clear()
{
_history.clear();
}
void EscapeSequenceUrlExtractor::setAllowedLinkSchema(const QStringList &schema)
{
_allowedUriSchemas = schema;
}
void EscapeSequenceUrlExtractor::historyLinesRemoved(int lines)
{
for (auto &url : _history) {
url.begin.row -= lines;
url.end.row -= lines;
}
_history.erase(std::remove_if(std::begin(_history),
std::end(_history),
[](const ExtractedUrl &url) {
const bool toRemove = url.begin.row < 0;
return toRemove;
}),
std::end(_history));
}
QVector<ExtractedUrl> EscapeSequenceUrlExtractor::history() const
{
return _history;
}
void Konsole::EscapeSequenceUrlExtractor::toggleUrlInput()
{
if (_ignoreNextUrlInput) {
_ignoreNextUrlInput = false;
return;
}
if (_reading) {
endUrlInput();
} else {
beginUrlInput();
}
}
}
#include "moc_EscapeSequenceUrlExtractor.cpp"
@@ -0,0 +1,133 @@
#ifndef ESCAPE_SEQUENCE_URL_EXTRACTOR_H
#define ESCAPE_SEQUENCE_URL_EXTRACTOR_H
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <QObject>
#include "konsoleprivate_export.h"
namespace Konsole
{
class Screen;
/* Like QPoint, but with Row / Col
* easier to read than x / y
*/
struct Coordinate {
int row;
int col;
};
/* Represents a URL on the visual part of konsole, that has been escaped.
* like a html url tag with a text value.
*/
struct ExtractedUrl {
QString url;
QString text;
Coordinate begin;
Coordinate end;
};
/* Stored in Screen, but used in V10Emulation to
* store extracted URL's. Perhaps this should be a Model?
*/
class KONSOLEPRIVATE_EXPORT EscapeSequenceUrlExtractor : public QObject
{
Q_OBJECT
private:
/* Tell us if we are currently reading or not a URL. */
bool _reading = false;
/* If we abort reading a URL input we enter in a invalid state,
* and we need to ignore the next toggle.
*/
bool _ignoreNextUrlInput = false;
/* The url / text pair being extracted currently */
ExtractedUrl _currentUrl{};
/* The maximum size of url to prevent a bomb
* that will take over the history file.
* TODO: make it configurable.
*/
// Not used ATM const int _maximumUrlHistory = 200;
/* All of the extracted URL's. */
QVector<ExtractedUrl> _history;
/* The URI schema format that's accepted */
QStringList _allowedUriSchemas;
/* Pointer to the Screen, that actually holds the text data. */
Screen *_screen = nullptr;
void appendUrlText_impl(uint c);
public:
/* This needs to have access to the Session
* calculate the row / col of the current URL.
*/
EscapeSequenceUrlExtractor();
/* This is a list of URI schemas that are going to be supported, separated by semicolon.
* like https://;file://
*/
void setAllowedLinkSchema(const QStringList &allowedSchemas);
void setScreen(Screen *screen);
/* If we are parsing a URL */
bool reading() const
{
return _reading;
}
/* We found an URL, starting to parse */
void beginUrlInput();
/* We received the end byte to finish the Url. */
void endUrlInput();
/* We are not saving this URL, it's bogus. */
void abortUrlInput();
/* The URL is parsed at once, but not the text. We received
* one character per time until we hit the end byte. */
void appendUrlText(uint c)
{
if (!reading()) {
return;
}
appendUrlText_impl(c);
}
/* The URL is parsed at once, store it at once. */
void setUrl(const QString &url);
/* All of the parsedURL's, used by TerminalDisplay to paint them
* on screen. */
QVector<ExtractedUrl> history() const;
/* Clear all the URL's, this is triggered when the Screen is cleared. */
void clear();
/* Iterates through all the URL's and remove the ones that are currently
* out of bounds because we removed lines in the History
*/
void historyLinesRemoved(int lines);
/* starts / stops URL Processing */
public Q_SLOTS:
void toggleUrlInput();
};
}
#endif
@@ -0,0 +1,97 @@
/*
SPDX-FileCopyrightText: 2018 Mariusz Glebocki <mglb@arccos-1.net>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "FontDialog.h"
// Qt
#include <QBoxLayout>
#include <QWhatsThis>
// KDE
#include <KLocalizedString>
using namespace Konsole;
FontDialog::FontDialog(QWidget *parent, bool emoji, const QFont font)
: QDialog(parent)
, _fontChooser(nullptr)
, _showAllFonts(nullptr)
, _buttonBox(nullptr)
, _emoji(emoji)
{
setWindowTitle(i18nc("@title:window", "Select font"));
KFontChooser::DisplayFlag onlyFixed = _emoji ? KFontChooser::FixedFontsOnly : KFontChooser::FixedFontsOnly;
_fontChooser = new KFontChooser(onlyFixed, this);
if (_emoji) {
QStringList list = KFontChooser::createFontList(0).filter(QStringLiteral("emoji"), Qt::CaseInsensitive);
_fontChooser->setFont(font);
_fontChooser->setFontListItems(KFontChooser::createFontList(0).filter(QStringLiteral("emoji"), Qt::CaseInsensitive));
_fontChooser->setFont(font);
}
_showAllFonts = new QCheckBox(i18nc("@action:button", "Show all fonts"), this);
_showAllFontsWarningButton = new QToolButton(this);
_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
if (_emoji) {
_fontChooser->setSampleText(
/* clang-format off */
QStringLiteral(" 🏴🤘🚬🌍🌎🌏🥆💣🗡🔫⚗️⚛️☢️☣️🌿🎱🏧💉💊🕴️📡🤻🦑🇦🇶👩‍🔬🪤🚱✊🏿🔬🧬🏴‍☠️🤽\n"
"0123456789\n"
"👆🏻 👆🏼 👆🏽 👆🏾 👆🏿 👨‍❤️‍👨 👨‍❤️‍💋‍👨 👩‍👩‍👧‍👧 👩🏻‍🤝‍👨🏿 👨‍👨‍👧‍👦 \U0001F468\u200D\u2764\uFE0F\u200D\U0001F468 \U0001F468\u200D\u2764\u200D\U0001F468\n"
"🇧🇲 🇨🇭 🇨🇿 🇪🇺 🇬🇱 🇲🇬 🇲🇹 🇸🇿 🇿🇲"));
/* clang-format on */
_showAllFonts->hide();
_showAllFontsWarningButton->hide();
} else {
_fontChooser->setSampleText(
QStringLiteral("0OQ 1Il!| 5S 8B rnm :; ,. \"'` ~-= ({[<>]})\n"
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\n"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n"
"abcdefghijklmnopqrstuvwxyz"));
_showAllFontsWarningButton->setIcon(QIcon::fromTheme(QStringLiteral("emblem-warning")));
_showAllFontsWarningButton->setAutoRaise(true);
connect(_showAllFonts, &QCheckBox::toggled, this, [this](bool enable) {
_fontChooser->setFont(_fontChooser->font(), !enable);
});
connect(_showAllFontsWarningButton, &QToolButton::clicked, this, [this](bool) {
const QString message =
i18nc("@info:status",
"By its very nature, a terminal program requires font characters that are equal width (monospace). Any non monospaced "
"font may cause display issues. This should not be necessary except in rare cases.");
const QPoint pos = QPoint(_showAllFonts->width() / 2, _showAllFonts->height());
QWhatsThis::showText(_showAllFonts->mapToGlobal(pos), message, _showAllFonts);
});
}
auto *showAllFontsLayout = new QHBoxLayout();
showAllFontsLayout->addWidget(_showAllFonts);
showAllFontsLayout->addWidget(_showAllFontsWarningButton);
showAllFontsLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
showAllFontsLayout->setContentsMargins(0, 0, 0, 0);
showAllFontsLayout->setSpacing(0);
auto *layout = new QVBoxLayout(this);
layout->addWidget(_fontChooser, 1);
if (!_emoji) {
layout->addLayout(showAllFontsLayout);
}
layout->addWidget(_buttonBox);
connect(_fontChooser, &KFontChooser::fontSelected, this, &FontDialog::fontChanged);
connect(_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void FontDialog::setFont(const QFont &font)
{
_fontChooser->setFont(font, !_showAllFonts->isChecked() && !_emoji);
}
#include "moc_FontDialog.cpp"
@@ -0,0 +1,44 @@
/*
SPDX-FileCopyrightText: 2018 Mariusz Glebocki <mglb@arccos-1.net>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef FONTDIALOG_H
#define FONTDIALOG_H
// Qt
#include <KFontChooser>
#include <QCheckBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QToolButton>
namespace Konsole
{
class FontDialog : public QDialog
{
Q_OBJECT
public:
explicit FontDialog(QWidget *parent = nullptr, bool emoji = false, const QFont font = QFont());
QFont font() const
{
return _fontChooser->font();
}
void setFont(const QFont &font);
Q_SIGNALS:
void fontChanged(const QFont &font);
private:
KFontChooser *_fontChooser;
QCheckBox *_showAllFonts;
QToolButton *_showAllFontsWarningButton;
QDialogButtonBox *_buttonBox;
bool _emoji;
};
}
#endif // FONTDIALOG_H
@@ -0,0 +1,79 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 2012 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "HistorySizeDialog.h"
// Konsole
#include "ui_HistorySizeDialog.h"
#include <KLocalizedString>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
using namespace Konsole;
HistorySizeDialog::HistorySizeDialog(QWidget *parent)
: QDialog(parent)
, _ui(nullptr)
{
setWindowTitle(i18nc("@title:window", "Adjust Scrollback"));
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto mainWidget = new QWidget(this);
auto mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
connect(buttonBox, &QDialogButtonBox::accepted, this, &HistorySizeDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &HistorySizeDialog::reject);
mainLayout->addWidget(buttonBox);
setWindowModality(Qt::WindowModal);
_ui = new Ui::HistorySizeDialog();
_ui->setupUi(mainWidget);
_ui->tempWarningWidget->setVisible(true);
_ui->tempWarningWidget->setWordWrap(false);
_ui->tempWarningWidget->setCloseButtonVisible(false);
_ui->tempWarningWidget->setMessageType(KMessageWidget::Information);
_ui->tempWarningWidget->setText(i18nc("@info:status", "Any adjustments are only temporary to this session."));
}
HistorySizeDialog::~HistorySizeDialog()
{
delete _ui;
}
void HistorySizeDialog::setMode(Enum::HistoryModeEnum aMode)
{
_ui->historySizeWidget->setMode(aMode);
}
Enum::HistoryModeEnum HistorySizeDialog::mode() const
{
return _ui->historySizeWidget->mode();
}
int HistorySizeDialog::lineCount() const
{
return _ui->historySizeWidget->lineCount();
}
void HistorySizeDialog::setLineCount(int lines)
{
_ui->historySizeWidget->setLineCount(lines);
}
QSize HistorySizeDialog::sizeHint() const
{
return {_ui->tempWarningWidget->sizeHint().width(), 0};
}
#include "moc_HistorySizeDialog.cpp"
@@ -0,0 +1,52 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 2012 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef HISTORYSIZEDIALOG_H
#define HISTORYSIZEDIALOG_H
// KDE
#include <QDialog>
// Konsole
#include "Enumeration.h"
#include "konsoleprivate_export.h"
namespace Ui
{
class HistorySizeDialog;
}
namespace Konsole
{
class KONSOLEPRIVATE_EXPORT HistorySizeDialog : public QDialog
{
Q_OBJECT
public:
explicit HistorySizeDialog(QWidget *parent = nullptr);
~HistorySizeDialog() override;
/** See HistorySizeWidget::setMode. */
void setMode(Enum::HistoryModeEnum aMode);
/** See HistorySizeWidget::mode. */
Enum::HistoryModeEnum mode() const;
/** See HistorySizeWidget::setLineCount. */
void setLineCount(int lines);
/** See HistorySizeWidget::lineCount. */
int lineCount() const;
QSize sizeHint() const override;
private:
Ui::HistorySizeDialog *_ui;
};
}
#endif // HISTORYSIZEDIALOG_H
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>HistorySizeDialog</class>
<widget class="QWidget" name="HistorySizeDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>48</height>
</rect>
</property>
<layout class="QVBoxLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="KMessageWidget" name="tempWarningWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="Konsole::HistorySizeWidget" name="historySizeWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KMessageWidget</class>
<extends>QFrame</extends>
<header>kmessagewidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>Konsole::HistorySizeWidget</class>
<extends>QWidget</extends>
<header>widgets/HistorySizeWidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,318 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "KeyBindingEditor.h"
// Qt
#include <QDialogButtonBox>
#include <QIcon>
#include <QKeyEvent>
// KDE
#include <KLocalizedString>
#include <KMessageBox>
// Konsole
#include "keyboardtranslator/KeyboardTranslator.h"
#include "keyboardtranslator/KeyboardTranslatorManager.h"
#include "keyboardtranslator/KeyboardTranslatorReader.h"
#include "ui_KeyBindingEditor.h"
#include "widgets/EditProfileDialog.h"
using namespace Konsole;
KeyBindingEditor::KeyBindingEditor(QWidget *parent)
: QDialog(parent)
, _ui(nullptr)
, _translator(new KeyboardTranslator(QString()))
, _isNewTranslator(false)
{
auto layout = new QVBoxLayout;
auto mainWidget = new QWidget(this);
layout->addWidget(mainWidget);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true);
layout->addWidget(buttonBox);
setLayout(layout);
connect(buttonBox, &QDialogButtonBox::accepted, this, &Konsole::KeyBindingEditor::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
setAttribute(Qt::WA_DeleteOnClose);
_ui = new Ui::KeyBindingEditor();
_ui->setupUi(mainWidget);
// description edit
_ui->descriptionEdit->setPlaceholderText(i18nc("@label:textbox", "Enter descriptive label"));
connect(_ui->descriptionEdit, &QLineEdit::textChanged, this, &Konsole::KeyBindingEditor::setTranslatorDescription);
// filter edit
connect(_ui->filterEdit, &QLineEdit::textChanged, this, &Konsole::KeyBindingEditor::filterRows);
// key bindings table
_ui->keyBindingTable->setColumnCount(2);
QStringList labels;
labels << i18n("Key Combination") << i18n("Output");
_ui->keyBindingTable->setHorizontalHeaderLabels(labels);
_ui->keyBindingTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
_ui->keyBindingTable->verticalHeader()->hide();
_ui->keyBindingTable->setSelectionBehavior(QAbstractItemView::SelectRows);
// add and remove buttons
_ui->addEntryButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
_ui->removeEntryButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
connect(_ui->removeEntryButton, &QPushButton::clicked, this, &Konsole::KeyBindingEditor::removeSelectedEntry);
connect(_ui->addEntryButton, &QPushButton::clicked, this, &Konsole::KeyBindingEditor::addNewEntry);
// test area
_ui->testAreaInputEdit->installEventFilter(this);
}
KeyBindingEditor::~KeyBindingEditor()
{
delete _ui;
delete _translator;
}
void KeyBindingEditor::filterRows(const QString &text)
{
const int rows = _ui->keyBindingTable->rowCount();
QList<int> matchedRows;
for (QTableWidgetItem *matchedItem : _ui->keyBindingTable->findItems(text, Qt::MatchContains)) {
matchedRows.append(matchedItem->row());
}
for (int i = 0; i < rows; i++) {
if (matchedRows.contains(i) && _ui->keyBindingTable->isRowHidden(i)) {
_ui->keyBindingTable->showRow(i);
} else if (!matchedRows.contains(i)) {
_ui->keyBindingTable->hideRow(i);
}
}
}
void KeyBindingEditor::removeSelectedEntry()
{
QList<QTableWidgetItem *> uniqueList;
const QList<QTableWidgetItem *> selectedItems = _ui->keyBindingTable->selectedItems();
for (QTableWidgetItem *item : selectedItems) {
if (item->column() == 1) { // Select item at the first column
item = _ui->keyBindingTable->item(item->row(), 0);
}
if (!uniqueList.contains(item)) {
uniqueList.append(item);
}
}
for (QTableWidgetItem *item : std::as_const(uniqueList)) {
// get the first item in the row which has the entry
KeyboardTranslator::Entry existing = item->data(Qt::UserRole).value<KeyboardTranslator::Entry>();
_translator->removeEntry(existing);
_ui->keyBindingTable->removeRow(item->row());
}
}
void KeyBindingEditor::addNewEntry()
{
_ui->keyBindingTable->insertRow(_ui->keyBindingTable->rowCount());
int newRowCount = _ui->keyBindingTable->rowCount();
// block signals here to avoid triggering bindingTableItemChanged() slot call
_ui->keyBindingTable->blockSignals(true);
_ui->keyBindingTable->setItem(newRowCount - 1, 0, new QTableWidgetItem());
_ui->keyBindingTable->setItem(newRowCount - 1, 1, new QTableWidgetItem());
_ui->keyBindingTable->blockSignals(false);
// make sure user can see new row
_ui->keyBindingTable->scrollToItem(_ui->keyBindingTable->item(newRowCount - 1, 0));
}
bool KeyBindingEditor::eventFilter(QObject *watched, QEvent *event)
{
if (watched == _ui->testAreaInputEdit) {
if (event->type() == QEvent::KeyPress) {
auto *keyEvent = static_cast<QKeyEvent *>(event);
// The state here is currently set to the state that a newly started
// terminal in Konsole will be in ( which is also the same as the
// state just after a reset ), this has 'ANSI' turned on and all other
// states off.
//
// TODO: It may be useful to be able to specify the state in the 'test input'
// area, but preferably not in a way which clutters the UI with lots of
// checkboxes.
//
const KeyboardTranslator::States states = KeyboardTranslator::AnsiState;
KeyboardTranslator::Entry entry = _translator->findEntry(keyEvent->key(), keyEvent->modifiers(), states);
if (!entry.isNull()) {
_ui->testAreaInputEdit->setText(entry.conditionToString());
_ui->testAreaOutputEdit->setText(entry.resultToString(true, keyEvent->modifiers()));
} else {
_ui->testAreaInputEdit->setText(keyEvent->text());
_ui->testAreaOutputEdit->setText(keyEvent->text());
}
keyEvent->accept();
return true;
}
}
return QDialog::eventFilter(watched, event);
}
void KeyBindingEditor::setDescription(const QString &description)
{
_ui->descriptionEdit->setText(description);
setTranslatorDescription(description);
}
void KeyBindingEditor::setTranslatorDescription(const QString &description)
{
if (_translator != nullptr) {
_translator->setDescription(description);
}
}
QString KeyBindingEditor::description() const
{
return _ui->descriptionEdit->text();
}
void KeyBindingEditor::setup(const KeyboardTranslator *translator, const QString &currentProfileTranslator, bool isNewTranslator)
{
delete _translator;
_isNewTranslator = isNewTranslator;
_currentProfileTranslator = currentProfileTranslator;
_translator = new KeyboardTranslator(*translator);
// setup description edit line
_ui->descriptionEdit->setClearButtonEnabled(true);
// setup filter edit line
_ui->filterEdit->setClearButtonEnabled(true);
if (_isNewTranslator) {
setDescription(i18n("New Key Binding List"));
setWindowTitle(i18n("New Key Binding List"));
} else {
_ui->descriptionEdit->setText(translator->description());
setWindowTitle(i18n("Edit Key Binding List"));
}
// setup key binding table
setupKeyBindingTable(translator);
}
KeyboardTranslator *KeyBindingEditor::translator() const
{
return _translator;
}
void KeyBindingEditor::bindingTableItemChanged(QTableWidgetItem *item)
{
QTableWidgetItem *key = _ui->keyBindingTable->item(item->row(), 0);
KeyboardTranslator::Entry existing = key->data(Qt::UserRole).value<KeyboardTranslator::Entry>();
QString condition = key->text();
QString result = _ui->keyBindingTable->item(item->row(), 1)->text();
KeyboardTranslator::Entry entry = KeyboardTranslatorReader::createEntry(condition, result);
_translator->replaceEntry(existing, entry);
// block signals to prevent this slot from being called repeatedly
_ui->keyBindingTable->blockSignals(true);
key->setData(Qt::UserRole, QVariant::fromValue(entry));
_ui->keyBindingTable->blockSignals(false);
}
void KeyBindingEditor::setupKeyBindingTable(const KeyboardTranslator *translator)
{
disconnect(_ui->keyBindingTable, &QTableWidget::itemChanged, this, &Konsole::KeyBindingEditor::bindingTableItemChanged);
QList<KeyboardTranslator::Entry> entries = translator->entries();
_ui->keyBindingTable->setRowCount(entries.count());
for (int row = 0; row < entries.count(); row++) {
const KeyboardTranslator::Entry &entry = entries.at(row);
QTableWidgetItem *keyItem = new QTableWidgetItem(entry.conditionToString());
keyItem->setData(Qt::UserRole, QVariant::fromValue(entry));
QTableWidgetItem *textItem = new QTableWidgetItem(entry.resultToString());
_ui->keyBindingTable->setItem(row, 0, keyItem);
_ui->keyBindingTable->setItem(row, 1, textItem);
}
_ui->keyBindingTable->sortItems(0);
connect(_ui->keyBindingTable, &QTableWidget::itemChanged, this, &Konsole::KeyBindingEditor::bindingTableItemChanged);
}
void KeyBindingEditor::accept()
{
if (_translator == nullptr) {
return;
}
const auto newTranslator = new KeyboardTranslator(*_translator);
if (newTranslator->description().isEmpty()) {
KMessageBox::error(this, i18n("A key bindings scheme cannot be saved with an empty description."));
delete newTranslator;
return;
}
if (_isNewTranslator) {
newTranslator->setName(newTranslator->description());
}
KeyboardTranslatorManager::instance()->addTranslator(newTranslator);
const QString &currentTranslatorName = newTranslator->name();
Q_EMIT updateKeyBindingsListRequest(currentTranslatorName);
if (currentTranslatorName == _currentProfileTranslator) {
Q_EMIT updateTempProfileKeyBindingsRequest(Profile::KeyBindings, currentTranslatorName);
}
QDialog::accept();
}
QSize KeyBindingEditor::sizeHint() const
{
const auto parent = parentWidget();
if (parent != nullptr) {
return {static_cast<int>(parent->width() * 0.9), static_cast<int>(parent->height() * 0.95)};
}
return {};
}
#include "moc_KeyBindingEditor.cpp"
@@ -0,0 +1,138 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KEYBINDINGEDITOR_H
#define KEYBINDINGEDITOR_H
// Qt
#include <QDialog>
// Konsole
// TODO: Move away Profile::Property from the profile header.
#include "profile/Profile.h"
class QTableWidgetItem;
namespace Ui
{
class KeyBindingEditor;
}
namespace Konsole
{
class KeyboardTranslator;
/**
* A dialog which allows the user to edit a key bindings scheme
* which maps between key combinations input by the user and
* the character sequence sent to the terminal when those
* combinations are pressed.
*
* The dialog can be initialized with the settings of an
* existing key bindings scheme using the setup() method.
*
* The dialog creates a copy of the supplied keyboard translator
* to which any changes are applied. The modified translator
* can be retrieved using the translator() method.
*/
class KeyBindingEditor : public QDialog
{
Q_OBJECT
public:
/** Constructs a new key bindings editor with the specified parent. */
explicit KeyBindingEditor(QWidget *parent = nullptr);
~KeyBindingEditor() override;
/**
* Initializes the dialog with the bindings and other settings
* from the specified @p translator.
* @p currentProfileTranslator the name of the translator set in the
* current profile
* @p isNewTranslator specifies whether the translator being edited
* is an already existing one or a newly created
* one, defaults to false.
*/
void setup(const KeyboardTranslator *translator, const QString &currentProfileTranslator, bool isNewTranslator = false);
/**
* Returns the modified translator describing the changes to the bindings
* and other settings which the user made.
*/
KeyboardTranslator *translator() const;
/**
* Sets the text of the editor's description field.
*/
void setDescription(const QString &description);
/**
* Returns the text of the editor's description field.
*/
QString description() const;
// reimplemented to handle test area input
bool eventFilter(QObject *watched, QEvent *event) override;
Q_SIGNALS:
/**
* Emitted when the user clicks the OK button to save the changes. This
* signal is connected to EditProfileDialog::updateKeyBindingsList()
* to update the key bindings list on the Keyboard tab in the Edit
* Profile dialog.
* @p translatorName is the translator that has just been edited
*/
void updateKeyBindingsListRequest(const QString &translatorName);
/**
* Emitted when the user clicks the OK button to save the changes to
* the translator that's set in the current profile; this signal is
* connected to EditProfileDialog::updateTempProfileProperty() to
* request applying the changes to the _tempProfile.
* @p newTranslatorName is the name of the translator, that has just
* been edited/saved, and which is also the translator
* set in the current Profile
*/
void updateTempProfileKeyBindingsRequest(Profile::Property, const QString &newTranslatorName);
private Q_SLOTS:
// reimplemented
void accept() override;
void setTranslatorDescription(const QString &description);
void bindingTableItemChanged(QTableWidgetItem *item);
void removeSelectedEntry();
void addNewEntry();
private:
Q_DISABLE_COPY(KeyBindingEditor)
void setupKeyBindingTable(const KeyboardTranslator *translator);
Ui::KeyBindingEditor *_ui;
// translator to which modifications are made as the user makes
// changes in the UI.
// this is initialized as a copy of the translator specified
// when setup() is called
KeyboardTranslator *_translator;
// Show only the rows that match the text entered by the user in the
// filter search box
void filterRows(const QString &text);
bool _isNewTranslator;
// The translator set in the current profile
QString _currentProfileTranslator;
// Sets the size hint of the dialog to be slightly smaller than the
// size of EditProfileDialog
QSize sizeHint() const override;
};
}
#endif // KEYBINDINGEDITOR_H
@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KeyBindingEditor</class>
<widget class="QWidget" name="KeyBindingEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>374</width>
<height>530</height>
</rect>
</property>
<layout class="QVBoxLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="descriptionLabel">
<property name="text">
<string>Description:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="descriptionEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="filterLabel">
<property name="text">
<string>Filter:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="filterEdit"/>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="keyBindingTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QPushButton" name="addEntryButton">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeEntryButton">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string comment="Title of the area where you test your keys are properly configured">Test Area</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QGridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Input:</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="testAreaInputEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Output:</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="testAreaOutputEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,115 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 2018 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "LabelsAligner.h"
// Qt
#include <QGridLayout>
#include <QWidget>
using namespace Konsole;
LabelsAligner::LabelsAligner(QWidget *refWidget)
: _refWidget(refWidget)
{
}
void LabelsAligner::addLayout(QGridLayout *layout)
{
_layouts.append(layout);
}
void LabelsAligner::addLayouts(const QVector<QGridLayout *> &layouts)
{
_layouts.append(layouts);
}
void LabelsAligner::setReferenceWidget(QWidget *refWidget)
{
_refWidget = refWidget;
}
void LabelsAligner::updateLayouts()
{
for (const auto *layout : std::as_const(_layouts)) {
QWidget *widget = layout->parentWidget();
Q_ASSERT(widget);
do {
QLayout *widgetLayout = widget->layout();
if (widgetLayout != nullptr) {
widgetLayout->update();
widgetLayout->activate();
}
widget = widget->parentWidget();
} while (widget != _refWidget && widget != nullptr);
}
}
void LabelsAligner::align()
{
Q_ASSERT(_refWidget);
if (_layouts.count() <= 1) {
return;
}
int maxRight = 0;
for (const auto *layout : std::as_const(_layouts)) {
int left = getLeftMargin(layout);
for (int row = 0; row < layout->rowCount(); ++row) {
QLayoutItem *layoutItem = layout->itemAtPosition(row, LABELS_COLUMN);
if (layoutItem == nullptr) {
continue;
}
QWidget *widget = layoutItem->widget();
if (widget == nullptr) {
continue;
}
const int idx = layout->indexOf(widget);
int rows, cols, rowSpan, colSpan;
layout->getItemPosition(idx, &rows, &cols, &rowSpan, &colSpan);
if (colSpan > 1) {
continue;
}
const int right = left + widget->sizeHint().width();
if (maxRight < right) {
maxRight = right;
}
}
}
for (auto *l : std::as_const(_layouts)) {
int left = getLeftMargin(l);
l->setColumnMinimumWidth(LABELS_COLUMN, maxRight - left);
}
}
int LabelsAligner::getLeftMargin(const QGridLayout *layout)
{
int left = layout->contentsMargins().left();
if (layout->parent()->isWidgetType()) {
auto *parentWidget = layout->parentWidget();
Q_ASSERT(parentWidget);
left += parentWidget->contentsMargins().left();
} else {
auto *parentLayout = qobject_cast<QLayout *>(layout->parent());
Q_ASSERT(parentLayout);
left += parentLayout->contentsMargins().left();
}
QWidget *parent = layout->parentWidget();
while (parent != _refWidget && parent != nullptr) {
left = parent->mapToParent(QPoint(left, 0)).x();
parent = parent->parentWidget();
}
return left;
}
#include "moc_LabelsAligner.cpp"
@@ -0,0 +1,54 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 2018 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef LABELSALIGNER_H
#define LABELSALIGNER_H
#include <QObject>
#include <QVector>
class QWidget;
class QGridLayout;
namespace Konsole
{
/**
* An utility class for aligning 0th column in multiple QGridLayouts.
*
* Limitations:
* - a layout can't be nested in another layout
* - reference widget must be an ancestor of all added layouts
* - only 0th column is processed (widgets spanning multiple columns
* are ignored)
*/
class LabelsAligner : public QObject
{
Q_OBJECT
public:
explicit LabelsAligner(QWidget *refWidget);
void addLayout(QGridLayout *layout);
void addLayouts(const QVector<QGridLayout *> &layouts);
void setReferenceWidget(QWidget *refWidget);
public Q_SLOTS:
void updateLayouts();
void align();
private:
int getLeftMargin(const QGridLayout *layout);
static constexpr int LABELS_COLUMN = 0;
QWidget *_refWidget;
QVector<QGridLayout *> _layouts;
};
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,235 @@
/*
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
// Qt
#include <QAction>
#include <QExplicitlySharedDataPointer>
#include <QPointer>
#include <QUrl>
// KDE
#include <KXmlGuiWindow>
// Konsole
#include "widgets/ViewSplitter.h"
#include "pluginsystem/IKonsolePlugin.h"
#include "konsoleapp_export.h"
#include <vector>
class KActionMenu;
class KHamburgerMenu;
class KToggleAction;
namespace Konsole
{
class ViewManager;
class ViewProperties;
class Session;
class SessionController;
class Profile;
class ProfileList;
class BookmarkHandler;
/**
* The main window. This contains the menus and an area which contains the terminal displays.
*
* The main window does not create the views or the container widgets which hold the views.
* This is done by the ViewManager class. When a MainWindow is instantiated, it creates
* a new ViewManager. The ViewManager can then be used to create new terminal displays
* inside the window.
*
* Do not construct new main windows directly, use Application's newMainWindow() method.
*/
class KONSOLEAPP_EXPORT MainWindow : public KXmlGuiWindow
{
Q_OBJECT
public:
/**
* Constructs a new main window. Do not create new main windows directly, use Application's
* newMainWindow() method instead.
*/
MainWindow();
/**
* Returns the view manager associated with this window. The view manager can be used to
* create new views on particular session objects inside this window.
*/
ViewManager *viewManager() const;
/**
* Create a new session.
*
* @param profile The profile to use to create the new session.
* @param directory Initial working directory for the new session or empty
* if the default working directory associated with the profile should be used.
*/
Session *createSession(QExplicitlySharedDataPointer<Profile> profile, const QString &directory);
/**
* create a new SSH session.
*
* @param profile The profile to use to create the new session.
* @param url the URL representing the new SSH connection
*/
Session *createSSHSession(QExplicitlySharedDataPointer<Profile> profile, const QUrl &url);
/**
* Helper method to make this window get input focus
*/
void setFocus();
/**
* Set the initial visibility of the menubar.
*/
void setMenuBarInitialVisibility(bool showMenuBar);
/**
* @brief Set the frameless state
*
* @param frameless If true, no titlebar or frame is displayed.
*/
void setRemoveWindowTitleBarAndFrame(bool frameless);
/**
* A reference to a plugin on the system.
*/
void addPlugin(IKonsolePlugin *plugin);
/**
* creates a new tab for the main window
*/
void newTab();
/**
* @brief set list actions for menu "Plugins"
*/
void setPluginsActions(const QList<QAction *> &actions);
Q_SIGNALS:
/**
* Emitted by the main window to request the creation of a
* new session in a new window.
*
* @param profile The profile to use to create the
* first session in the new window.
* @param directory Initial working directory for the new window or empty
* if the default working directory associated with the profile should
* be used.
*/
void newWindowRequest(const QExplicitlySharedDataPointer<Profile> &profile, const QString &directory);
/**
* Emitted when a view for one session is detached from this window
*/
void terminalsDetached(ViewSplitter *splitter, QHash<TerminalDisplay *, Session *> sessionsMap);
protected:
// Reimplemented for internal reasons.
void showEvent(QShowEvent *event) override;
bool eventFilter(QObject *obj, QEvent *event) override;
// reimplemented from KMainWindow
bool queryClose() override;
void saveProperties(KConfigGroup &group) override;
void readProperties(const KConfigGroup &group) override;
void saveGlobalProperties(KConfig *config) override;
void readGlobalProperties(KConfig *config) override;
// reimplemented from QWidget
bool focusNextPrevChild(bool next) override;
protected Q_SLOTS:
void saveNewToolbarConfig() override;
private Q_SLOTS:
void cloneTab();
void newWindow();
void showManageProfilesDialog();
void activateMenuBar();
void showSettingsDialog(bool showProfilePage = false);
void showShortcutsDialog();
void newFromProfile(const QExplicitlySharedDataPointer<Profile> &profile);
void activeViewChanged(SessionController *controller);
void disconnectController(SessionController *controller);
void activeViewTitleChanged(ViewProperties *);
void profileListChanged(const QList<QAction *> &sessionActions);
void configureNotifications();
void setBlur(bool blur);
void updateWindowIcon();
void updateWindowCaption();
void openUrls(const QList<QUrl> &urls);
// Sets the list of profiles to be displayed under the "New Tab" action
void setProfileList(ProfileList *list);
void applyKonsoleSettings();
// Ask the window manager to show this application window
void activationRequest(const QString &xdgActivationToken);
void updateUseTransparency();
public Q_SLOTS:
void viewFullScreen(bool fullScreen);
private:
void applyMainWindowSettings(const KConfigGroup &config) override;
/**
* Returns true if the window geometry was previously saved to the
* config file, false otherwise.
*/
bool wasWindowGeometrySaved() const;
void correctStandardShortcuts();
void rememberMenuAccelerators();
void removeMenuAccelerators();
void restoreMenuAccelerators();
void setupActions();
QString activeSessionDir() const;
void triggerAction(const QString &name) const;
/**
* Returns the bookmark handler associated with this window.
*/
BookmarkHandler *bookmarkHandler() const;
// sets the active shortcuts of actions in 'dest' to the shortcuts of actions
// with the same name in 'source' (see QAction::ActiveShortcut)
static void syncActiveShortcuts(KActionCollection *dest, const KActionCollection *source);
void updateHamburgerMenu();
private:
ViewManager *_viewManager;
BookmarkHandler *_bookmarkHandler;
KToggleAction *_toggleMenuBarAction;
KActionMenu *_newTabMenuAction;
KHamburgerMenu *_hamburgerMenu;
QPointer<SessionController> _pluggedController;
std::vector<IKonsolePlugin *> _plugins;
QList<QAction *> _pluginsActions;
bool _blurEnabled = false;
bool _firstShowEvent = true;
struct {
bool enabled = false; // indicates that we got a command line argument for menubar
bool showMenuBar = true;
} _windowArgsMenuBarVisible;
};
}
#endif // MAINWINDOW_H
@@ -0,0 +1,15 @@
#! /bin/sh
rm -f schemas.cpp
for i in ../data/color-schemes/*.colorscheme; do
grep "^Description=" $i | sed -e 's#^Description=\(.*\)$#i18n(\"\1\")#' >> schemas.cpp
done
for i in ../data/keyboard-layouts/*.keytab; do
grep "^keyboard" $i | sed -e 's#^keyboard \"\(.*\)\"$#i18n(\"\1\")#' >> schemas.cpp
done
$EXTRACTRC `find . -name \*.ui` >> rc.cpp
$EXTRACTRC `find . -name \*.kcfg` >> rc.cpp
$EXTRACTRC `find ../desktop -name \*.rc` >> rc.cpp
$XGETTEXT `find . -name \*.cpp -o -name \*.h | grep -v '/tests/'` -o $podir/konsole.pot
rm -f schemas.cpp
#rm -f tips.cpp
rm -f rc.cpp
@@ -0,0 +1,37 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.countm>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "NullProcessInfo.h"
using namespace Konsole;
NullProcessInfo::NullProcessInfo(int pid)
: ProcessInfo(pid)
{
}
void NullProcessInfo::readProcessInfo(int /*pid*/)
{
}
bool NullProcessInfo::readProcessName(int /*pid*/)
{
return false;
}
bool NullProcessInfo::readCurrentDir(int /*pid*/)
{
return false;
}
bool NullProcessInfo::readArguments(int /*pid*/)
{
return false;
}
void NullProcessInfo::readUserName()
{
}
@@ -0,0 +1,40 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.countm>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef NULLPROCESSINFO_H
#define NULLPROCESSINFO_H
#include "ProcessInfo.h"
namespace Konsole
{
/**
* Implementation of ProcessInfo which does nothing.
* Used on platforms where a suitable ProcessInfo subclass is not
* available.
*
* isValid() will always return false for instances of NullProcessInfo
*/
class NullProcessInfo : public ProcessInfo
{
public:
/**
* Constructs a new NullProcessInfo instance.
* See ProcessInfo::newInstance()
*/
explicit NullProcessInfo(int pid);
protected:
void readProcessInfo(int pid) override;
bool readProcessName(int pid) override;
bool readCurrentDir(int pid) override;
bool readArguments(int pid) override;
void readUserName(void) override;
};
}
#endif
@@ -0,0 +1,395 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "Part.h"
// Qt
#include <QDir>
#include <QKeyEvent>
#include <QMetaEnum>
#include <QStringList>
#include <QUrl>
// KDE
#include <KActionCollection>
#include <KConfigDialog>
#include <KLocalizedString>
#include <KPluginFactory>
#include <QAction>
// Konsole
#include "Emulation.h"
#include "KonsoleSettings.h"
#include "ViewManager.h"
#include "profile/ProfileManager.h"
#include "session/SessionController.h"
#include "session/SessionManager.h"
#include "settings/PartInfo.h"
#include "settings/ProfileSettings.h"
#include "terminalDisplay/TerminalDisplay.h"
#include "widgets/EditProfileDialog.h"
#include "widgets/ViewContainer.h"
using namespace Konsole;
K_PLUGIN_FACTORY_WITH_JSON(KonsolePartFactory, "konsolepart.json", registerPlugin<Konsole::Part>();)
Part::Part(QObject *parent, const QVariantList &)
: KParts::ReadOnlyPart(parent)
, _viewManager(nullptr)
, _pluggedController(nullptr)
{
// create view widget
_viewManager = new ViewManager(this, actionCollection());
_viewManager->setNavigationMethod(ViewManager::NoNavigation);
connect(_viewManager, &Konsole::ViewManager::activeViewChanged, this, &Konsole::Part::activeViewChanged);
connect(_viewManager, &Konsole::ViewManager::empty, this, &Konsole::Part::terminalExited);
connect(_viewManager, &Konsole::ViewManager::newViewRequest, this, &Konsole::Part::newTab);
_viewManager->widget()->setParent(widget());
setWidget(_viewManager->widget());
actionCollection()->addAssociatedWidget(_viewManager->widget());
const QList<QAction *> actionsList = actionCollection()->actions();
for (QAction *action : actionsList) {
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
}
// Enable translucency support if supported by the app.
if (_viewManager->widget()->window() && _viewManager->widget()->window()->testAttribute(Qt::WA_TranslucentBackground)) {
_viewManager->widget()->setAttribute(Qt::WA_TranslucentBackground, true);
}
// create basic session
createSession();
}
Part::~Part() = default;
bool Part::openFile()
{
return false;
}
void Part::terminalExited()
{
deleteLater();
}
void Part::newTab()
{
createSession();
}
Session *Part::activeSession() const
{
if (_viewManager->activeViewController() != nullptr) {
Q_ASSERT(_viewManager->activeViewController()->session());
return _viewManager->activeViewController()->session();
}
return nullptr;
}
void Part::startProgram(const QString &program, const QStringList &arguments)
{
Q_ASSERT(activeSession());
// do nothing if the session has already started running
if (activeSession()->isRunning()) {
return;
}
if (!program.isEmpty() && !arguments.isEmpty()) {
activeSession()->setProgram(program);
activeSession()->setArguments(arguments);
}
activeSession()->run();
}
void Part::openTeletype(int ptyMasterFd, bool runShell)
{
Q_ASSERT(activeSession());
activeSession()->openTeletype(ptyMasterFd, runShell);
}
void Part::showShellInDir(const QString &dir)
{
Q_ASSERT(activeSession());
// do nothing if the session has already started running
if (activeSession()->isRunning()) {
return;
}
// All other checking is done in setInitialWorkingDirectory()
if (!dir.isEmpty()) {
activeSession()->setInitialWorkingDirectory(dir);
}
activeSession()->run();
}
void Part::sendInput(const QString &text)
{
Q_ASSERT(activeSession());
activeSession()->sendTextToTerminal(text);
}
int Part::terminalProcessId()
{
Q_ASSERT(activeSession());
return activeSession()->processId();
}
int Part::foregroundProcessId()
{
Q_ASSERT(activeSession());
if (activeSession()->isForegroundProcessActive()) {
return activeSession()->foregroundProcessId();
}
return -1;
}
QString Part::foregroundProcessName()
{
Q_ASSERT(activeSession());
if (activeSession()->isForegroundProcessActive()) {
return activeSession()->foregroundProcessName();
}
return QString();
}
QString Part::currentWorkingDirectory() const
{
Q_ASSERT(activeSession());
return activeSession()->currentWorkingDirectory();
}
QVariant Part::profileProperty(const QString &profileProperty) const
{
const auto metaEnum = QMetaEnum::fromType<Profile::Property>();
const auto value = metaEnum.keyToValue(profileProperty.toStdString().c_str());
if (value == -1) {
return QString();
}
const auto p = static_cast<Profile::Property>(value);
return SessionManager::instance()->sessionProfile(activeSession())->property<QVariant>(p);
}
QStringList Part::availableProfiles() const
{
return ProfileManager::instance()->availableProfileNames();
}
QString Part::currentProfileName() const
{
return SessionManager::instance()->sessionProfile(activeSession())->name();
}
bool Part::setCurrentProfile(const QString &profileName)
{
Profile::Ptr profile;
for (auto p : ProfileManager::instance()->allProfiles()) {
if (p->name() == profileName) {
profile = p;
break;
}
}
if (!profile) {
profile = ProfileManager::instance()->loadProfile(profileName);
}
SessionManager::instance()->setSessionProfile(activeSession(), profile);
return currentProfileName() == profileName;
}
void Part::createSession(const QString &profileName, const QString &directory)
{
Profile::Ptr profile = ProfileManager::instance()->defaultProfile();
if (!profileName.isEmpty()) {
profile = ProfileManager::instance()->loadProfile(profileName);
}
Q_ASSERT(profile);
Session *session = SessionManager::instance()->createSession(profile);
// override the default directory specified in the profile
if (!directory.isEmpty() && profile->startInCurrentSessionDir()) {
session->setInitialWorkingDirectory(directory);
}
auto newView = _viewManager->createView(session);
_viewManager->activeContainer()->addView(newView);
}
void Part::activeViewChanged(SessionController *controller)
{
Q_ASSERT(controller);
Q_ASSERT(controller->view());
// remove existing controller
if (_pluggedController != nullptr) {
removeChildClient(_pluggedController);
disconnect(_pluggedController, &Konsole::SessionController::titleChanged, this, &Konsole::Part::activeViewTitleChanged);
disconnect(_pluggedController, &Konsole::SessionController::currentDirectoryChanged, this, &Konsole::Part::currentDirectoryChanged);
}
// insert new controller
insertChildClient(controller);
connect(controller, &Konsole::SessionController::titleChanged, this, &Konsole::Part::activeViewTitleChanged);
activeViewTitleChanged(controller);
connect(controller, &Konsole::SessionController::currentDirectoryChanged, this, &Konsole::Part::currentDirectoryChanged);
disconnect(controller->view(), &TerminalDisplay::overrideShortcutCheck, this, &Part::overrideTerminalShortcut);
connect(controller->view(), &TerminalDisplay::overrideShortcutCheck, this, &Part::overrideTerminalShortcut);
_pluggedController = controller;
}
void Part::overrideTerminalShortcut(QKeyEvent *event, bool &override)
{
// Shift+Insert is commonly used as the alternate shortcut for
// pasting in KDE apps(including konsole), so it deserves some
// special treatment.
if (((event->modifiers() & Qt::ShiftModifier) != 0U) && (event->key() == Qt::Key_Insert)) {
override = false;
return;
}
// override all shortcuts in the embedded terminal by default
override = true;
Q_EMIT overrideShortcut(event, override);
}
void Part::activeViewTitleChanged(ViewProperties *properties)
{
Q_EMIT setWindowCaption(properties->title());
}
void Part::showManageProfilesDialog(QWidget *parent)
{
// Make sure this string is unique among all users of this part
if (KConfigDialog::showDialog(QStringLiteral("konsolepartmanageprofiles"))) {
return;
}
KConfigDialog *settingsDialog = new KConfigDialog(parent, QStringLiteral("konsolepartmanageprofiles"), KonsoleSettings::self());
settingsDialog->setFaceType(KPageDialog::Tabbed);
auto profileSettings = new ProfileSettings(settingsDialog);
settingsDialog->addPage(profileSettings, i18nc("@title Preferences page name", "Profiles"), QStringLiteral("configure"));
auto partInfoSettings = new PartInfoSettings(settingsDialog);
settingsDialog->addPage(partInfoSettings, i18nc("@title Preferences page name", "Part Info"), QStringLiteral("dialog-information"));
settingsDialog->show();
}
void Part::showEditCurrentProfileDialog(QWidget *parent)
{
Q_ASSERT(activeSession());
auto dialog = new EditProfileDialog(parent);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setProfile(SessionManager::instance()->sessionProfile(activeSession()));
dialog->show();
}
void Part::changeSessionSettings(const QString &text)
{
Q_ASSERT(activeSession());
// send a profile change command, the escape code format
// is the same as the normal X-Term commands used to change the window title or icon,
// but with a magic value of '50' for the parameter which specifies what to change
QString command = QStringLiteral("\033]50;%1\a").arg(text);
sendInput(command);
}
// Konqueror integration
bool Part::openUrl(const QUrl &url)
{
if (KParts::ReadOnlyPart::url() == url) {
Q_EMIT completed();
return true;
}
setUrl(url);
Q_EMIT setWindowCaption(url.toDisplayString(QUrl::PreferLocalFile));
////qDebug() << "Set Window Caption to " << url.pathOrUrl();
Q_EMIT started(nullptr);
if (url.isLocalFile()) {
showShellInDir(url.path());
} else {
showShellInDir(QDir::homePath());
}
Q_EMIT completed();
return true;
}
void Part::setMonitorSilenceEnabled(bool enabled)
{
Q_ASSERT(activeSession());
if (enabled) {
activeSession()->setMonitorSilence(true);
connect(activeSession(), &Konsole::Session::notificationsChanged, this, &Konsole::Part::notificationChanged, Qt::UniqueConnection);
} else {
activeSession()->setMonitorSilence(false);
if (!activeSession()->isMonitorActivity()) {
disconnect(activeSession(), &Konsole::Session::notificationsChanged, this, &Konsole::Part::notificationChanged);
}
}
}
void Part::setMonitorActivityEnabled(bool enabled)
{
Q_ASSERT(activeSession());
if (enabled) {
activeSession()->setMonitorActivity(true);
connect(activeSession(), &Konsole::Session::notificationsChanged, this, &Konsole::Part::notificationChanged, Qt::UniqueConnection);
} else {
activeSession()->setMonitorActivity(false);
if (!activeSession()->isMonitorSilence()) {
disconnect(activeSession(), &Konsole::Session::notificationsChanged, this, &Konsole::Part::notificationChanged);
}
}
}
bool Part::isBlurEnabled()
{
return ViewManager::profileHasBlurEnabled(SessionManager::instance()->sessionProfile(activeSession()));
}
void Part::notificationChanged(Session::Notification notification, bool enabled)
{
if (notification == Session::Notification::Silence && enabled) {
Q_EMIT silenceDetected();
} else if (notification == Session::Notification::Activity && enabled) {
Q_EMIT activityDetected();
}
}
#include "Part.moc"
#include "moc_Part.cpp"
+202
View File
@@ -0,0 +1,202 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef PART_H
#define PART_H
// KDE
#include <KParts/ReadOnlyPart>
#include <kde_terminal_interface.h>
// Qt
#include <QVariantList>
// Konsole
#include "config-konsole.h"
#include "session/Session.h"
class QKeyEvent;
namespace Konsole
{
class Session;
class SessionController;
class ViewManager;
class ViewProperties;
/**
* A re-usable terminal emulator component using the KParts framework which can
* be used to embed terminal emulators into other applications.
*/
class Part : public KParts::ReadOnlyPart, public TerminalInterface
{
Q_OBJECT
Q_INTERFACES(TerminalInterface)
public:
/** Constructs a new Konsole part with the specified parent. */
explicit Part(QObject *parent, const QVariantList &);
~Part() override;
/** Reimplemented from TerminalInterface. */
void startProgram(const QString &program, const QStringList &arguments) override;
/** Reimplemented from TerminalInterface. */
void showShellInDir(const QString &dir) override;
/** Reimplemented from TerminalInterface. */
void sendInput(const QString &text) override;
/** Reimplemented from TerminalInterface. */
int terminalProcessId() override;
/** Reimplemented from TerminalInterface. */
int foregroundProcessId() override;
/** Reimplemented from TerminalInterface. */
QString foregroundProcessName() override;
/** Reimplemented from TerminalInterface. */
QString currentWorkingDirectory() const override;
/** Reimplemented from TerminalInterfaceV2 */
QStringList availableProfiles() const override;
/** Reimplemented from TerminalInterfaceV2 */
QString currentProfileName() const override;
/** Reimplemented from TerminalInterfaceV2 */
bool setCurrentProfile(const QString &profileName) override;
/** Reimplemented from TerminalInterfaceV2 */
QVariant profileProperty(const QString &profileProperty) const override;
public Q_SLOTS:
/**
* creates and run a session using the specified profile and directory
*
* @param profileName Specifies the name of the profile to create session
* @param directory specifies The initial working directory of the created session
*
* This is highly experimental. Do not use it at the moment
*/
void createSession(const QString &profileName = QString(), const QString &directory = QString());
void showManageProfilesDialog(QWidget *parent);
/**
* Shows the dialog used to edit the profile used by the active session. The
* dialog will be non-modal and will delete itself when it is closed.
*
* This is experimental API and not guaranteed to be present in later KDE 4
* releases.
*
* @param parent The parent widget of the new dialog.
*/
void showEditCurrentProfileDialog(QWidget *parent);
/**
* Sends a profile change command to the active session. This is equivalent to using
* the konsoleprofile tool within the session to change its settings. The @p text string
* is a semi-colon separated list of property=value pairs, eg. "colors=Linux Colors"
*
* See the documentation for konsoleprofile for information on the format of @p text
*
* This is experimental API and not guaranteed to be present in later KDE 4 releases.
*/
void changeSessionSettings(const QString &text);
/**
* Connects to an existing pseudo-teletype. See Session::openTeletype().
* This must be called before the session is started by startProgram(),
* or showShellInDir()
*
* @param ptyMasterFd The file descriptor of the pseudo-teletype (pty) master
* @param runShell When true (default, legacy), runs the teletype in a shell
* session environment. When false, the session is not run, so that the
* KPtyProcess can be standalone, which may be useful for interactive programs.
*/
void openTeletype(int ptyMasterFd, bool runShell = true);
/**
* Toggles monitoring for silence in the active session. If silence is detected,
* the silenceDetected() signal is emitted.
*
* @param enabled Whether to enable or disable monitoring for silence.
* */
void setMonitorSilenceEnabled(bool enabled);
/**
* Toggles monitoring for activity in the active session. If activity is detected,
* the activityDetected() signal is emitted.
*
* @param enabled Whether to enable or disable monitoring for activity.
* */
void setMonitorActivityEnabled(bool enabled);
/**
* Returns the status of the blur of the current profile.
*
* @return True if blur is enabled for the current active Konsole color profile.
* */
bool isBlurEnabled();
Q_SIGNALS:
/**
* Emitted when the key sequence for a shortcut, which is also a valid terminal key sequence,
* is pressed while the terminal has focus. By responding to this signal, the
* controlling application can choose whether to execute the action associated with
* the shortcut or ignore the shortcut and send the key
* sequence to the terminal application.
*
* In the embedded terminal, shortcuts are overridden and sent to the terminal by default.
* Set @p override to false to prevent this happening and allow the shortcut to be triggered
* normally.
*
* overrideShortcut() is not called for shortcuts which are not valid terminal key sequences,
* eg. shortcuts with two or more modifiers.
*
* @param event Describes the keys that were pressed.
* @param override Set this to false to prevent the terminal display from overriding the shortcut
*/
void overrideShortcut(QKeyEvent *event, bool &override);
/**
* Emitted when silence has been detected in the active session. Monitoring
* for silence has to be enabled first using setMonitorSilenceEnabled().
*/
void silenceDetected();
/**
* Emitted when activity has been detected in the active session. Monitoring
* for activity has to be enabled first using setMonitorActivityEnabled().
*/
void activityDetected();
/**
* Emitted when the current working directory of the active session has changed.
*/
void currentDirectoryChanged(const QString &dir);
protected:
/** Reimplemented from KParts::PartBase. */
bool openFile() override;
bool openUrl(const QUrl &url) override;
private Q_SLOTS:
void activeViewChanged(SessionController *controller);
void activeViewTitleChanged(ViewProperties *properties);
void terminalExited();
void newTab();
void overrideTerminalShortcut(QKeyEvent *, bool &override);
void notificationChanged(Session::Notification notification, bool enabled);
private:
Session *activeSession() const;
private:
ViewManager *_viewManager;
SessionController *_pluggedController;
};
}
#endif // PART_H
@@ -0,0 +1,48 @@
/*
This source file is part of Konsole, a terminal emulator.
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef POPSTACKONEXIT_H
#define POPSTACKONEXIT_H
#include <QStack>
namespace Konsole
{
/**
* PopStackOnExit is a utility to remove all values from a QStack which are added during
* the lifetime of a PopStackOnExit instance.
*
* When a PopStackOnExit instance is destroyed, elements are removed from the stack
* until the stack count is reduced the value when the PopStackOnExit instance was created.
*/
template<class T>
class PopStackOnExit
{
public:
explicit PopStackOnExit(QStack<T> &stack)
: _stack(stack)
, _count(stack.count())
{
}
~PopStackOnExit()
{
while (_stack.count() > _count) {
_stack.pop();
}
}
private:
Q_DISABLE_COPY(PopStackOnExit)
QStack<T> &_stack;
int _count;
};
}
#endif
@@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: 2012 Kasper Laudrup <laudrup@stacktrace.dk>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
// Own
#include "PrintOptions.h"
// KDE
#include <KConfigGroup>
#include <KSharedConfig>
using namespace Konsole;
PrintOptions::PrintOptions(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("PrintOptions"));
printerFriendly->setChecked(configGroup.readEntry("PrinterFriendly", true));
scaleOutput->setChecked(configGroup.readEntry("ScaleOutput", true));
}
PrintOptions::~PrintOptions() = default;
void PrintOptions::saveSettings()
{
KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("PrintOptions"));
configGroup.writeEntry("PrinterFriendly", printerFriendly->isChecked());
configGroup.writeEntry("ScaleOutput", scaleOutput->isChecked());
}
#include "moc_PrintOptions.cpp"
@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2012 Kasper Laudrup <laudrup@stacktrace.dk>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef PRINTOPTIONS_H
#define PRINTOPTIONS_H
#include "ui_PrintOptions.h"
namespace Konsole
{
class PrintOptions : public QWidget, private Ui::PrintOptions
{
Q_OBJECT
public:
explicit PrintOptions(QWidget *parent = nullptr);
~PrintOptions() override;
public Q_SLOTS:
void saveSettings();
};
}
#endif
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PrintOptions</class>
<widget class="QWidget" name="PrintOptions">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>573</width>
<height>475</height>
</rect>
</property>
<property name="windowTitle">
<string>Output Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="OutputOptions">
<property name="title">
<string>Output Options</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>223</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="printerFriendly">
<property name="text">
<string>Printer &amp;friendly mode (black text, no background)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="scaleOutput">
<property name="text">
<string>&amp;Scale output</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,304 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef PROCESSINFO_H
#define PROCESSINFO_H
// Qt
#include <QFile>
#include <QString>
#include <QVector>
#include "konsoleprivate_export.h"
namespace Konsole
{
/**
* Takes a snapshot of the state of a process and provides access to
* information such as the process name, parent process,
* the foreground process in the controlling terminal,
* the arguments with which the process was started.
*
* To create a new snapshot, construct a new ProcessInfo instance,
* using ProcessInfo::newInstance(),
* passing the process identifier of the process you are interested in.
*
* After creating a new instance, call the update() method to take a
* snapshot of the current state of the process.
*
* Before calling any additional methods, check that the process state
* was read successfully using the isValid() method.
*
* Each accessor method which provides information about the process state ( such as pid(),
* currentDir(), name() ) takes a pointer to a boolean as an argument. If the information
* requested was read successfully then the boolean is set to true, otherwise it is set
* to false, in which case the return value from the function should be ignored.
* If this boolean is set to false, it may indicate an error reading the process information,
* or it may indicate that the information is not available on the current platform.
*
* eg.
*
* @code
* ProcessInfo* info = ProcessInfo::newInstance(pid);
* info->update();
*
* if ( info->isValid() )
* {
* bool ok;
*
* QString name = info->name(&ok);
* if ( ok ) qDebug() << "process name - " << name;
* int parentPid = info->parentPid(&ok);
* if ( ok ) qDebug() << "parent process - " << parentPid;
* int foregroundPid = info->foregroundPid(&ok);
* if ( ok ) qDebug() << "foreground process - " << foregroundPid;
* }
* @endcode
*/
class KONSOLEPRIVATE_EXPORT ProcessInfo
{
public:
/**
* Constructs a new instance of a suitable ProcessInfo sub-class for
* the current platform which provides information about a given process.
*
* @param pid The pid of the process to examine
* @param sessionPid -1 if examined process is session process,
* else will be pid of session process of the tab housing examined process
*/
static ProcessInfo *newInstance(int pid, int sessionPid = -1);
virtual ~ProcessInfo()
{
}
/**
* Updates the information about the process. This must
* be called before attempting to use any of the accessor methods.
*/
void update();
/**
* Reads the most recent arguments passed to the process.
*/
void refreshArguments();
/** Returns true if the process state was read successfully. */
bool isValid() const;
/**
* Returns the process id.
*
* @param ok Set to true if the process id was read successfully or false otherwise
*/
int pid(bool *ok) const;
/**
* Returns the id of the parent process id was read successfully or false otherwise
*
* @param ok Set to true if the parent process id
*/
int parentPid(bool *ok) const;
/**
* Returns the id of the current foreground process
*
* NOTE: Using the foregroundProcessGroup() method of the Pty
* instance associated with the terminal of interest is preferred
* over using this method.
*
* @param ok Set to true if the foreground process id was read successfully or false otherwise
*/
int foregroundPid(bool *ok) const;
/* Returns the user id of the process */
int userId(bool *ok) const;
/** Returns the user's name of the process */
QString userName() const;
/** Returns the user's home directory of the process */
QString userHomeDir() const;
/** Returns the local host */
static QString localHost();
/** Returns the name of the current process */
QString name(bool *ok) const;
/**
* Returns the command-line arguments which the process
* was started with.
*
* The first argument is the name used to launch the process.
*
* @param ok Set to true if the arguments were read successfully or false otherwise.
*/
QVector<QString> arguments(bool *ok) const;
/**
* Returns the current working directory of the process
*
* @param ok Set to true if the current working directory was read successfully or false otherwise
*/
QString currentDir(bool *ok) const;
/**
* Returns the current working directory of the process (or its parent)
*/
QString validCurrentDir() const;
/** Forces the user home directory to be calculated */
void setUserHomeDir();
/**
* This enum describes the errors which can occur when trying to read
* a process's information.
*/
enum Error {
/** No error occurred. */
NoError,
/** The nature of the error is unknown. */
UnknownError,
/** Konsole does not have permission to obtain the process information. */
PermissionsError,
};
/**
* Returns the last error which occurred.
*/
Error error() const;
enum Field {
PROCESS_ID = 1,
PARENT_PID = 2,
FOREGROUND_PID = 4,
ARGUMENTS = 8,
NAME = 16,
CURRENT_DIR = 32,
UID = 64,
};
Q_DECLARE_FLAGS(Fields, Field)
// takes a full directory path and returns a
// shortened version suitable for display in
// space-constrained UI elements (eg. tabs)
QString formatShortDir(const QString &dirPath) const;
void setUserNameRequired(bool need);
protected:
/**
* Constructs a new process instance. You should not call the constructor
* of ProcessInfo or its subclasses directly. Instead use the
* static ProcessInfo::newInstance() method which will return
* a suitable ProcessInfo instance for the current platform.
*/
explicit ProcessInfo(int pid);
/**
* This is called on construction to read the process state
* Subclasses should reimplement this function to provide
* platform-specific process state reading functionality.
*
* When called, readProcessInfo() should attempt to read all
* of the necessary state information. If the attempt is successful,
* it should set the process id using setPid(), and update
* the other relevant information using setParentPid(), setName(),
* setArguments() etc.
*
* Calls to isValid() will return true only if the process id
* has been set using setPid()
*
* @param pid The process id of the process to read
*/
virtual void readProcessInfo(int pid) = 0;
/**
* Only reads and updates the name of the process
* @param pid process ID to use
* @return true on success
*/
virtual bool readProcessName(int pid) = 0;
/**
* Determine the current directory of the process.
* @param pid process ID to use
* @return true on success
*/
virtual bool readCurrentDir(int pid) = 0;
/**
* Determine what arguments were passed to the process.
* Sets _arguments.
* @param pid process ID to use
* @return true on success
*/
virtual bool readArguments(int pid) = 0;
/* Read the user name */
virtual void readUserName(void) = 0;
/** Sets the process id associated with this ProcessInfo instance */
void setPid(int pid);
/** Sets the parent process id as returned by parentPid() */
void setParentPid(int pid);
/** Sets the foreground process id as returned by foregroundPid() */
void setForegroundPid(int pid);
/** Sets the user id associated with this ProcessInfo instance */
void setUserId(int uid);
/** Sets the user name of the process as set by readUserName() */
void setUserName(const QString &name);
/** Sets the name of the process as returned by name() */
void setName(const QString &name);
/** Sets the current working directory for the process */
void setCurrentDir(const QString &dir);
/** Sets the error */
void setError(Error error);
/** Convenience method. Sets the error based on a QFile error code. */
void setFileError(QFile::FileError error);
/**
* Adds a commandline argument for the process, as returned
* by arguments()
*/
void addArgument(const QString &argument);
/**
* clear the commandline arguments for the process, as returned
* by arguments()
*/
void clearArguments();
bool userNameRequired() const;
private:
Fields _fields;
int _pid;
int _parentPid;
int _foregroundPid;
int _userId;
Error _lastError;
QString _name;
QString _userName;
QString _userHomeDir;
QString _currentDir;
bool _userNameRequired;
QVector<QString> _arguments;
static QStringList commonDirNames();
static QStringList _commonDirNames;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessInfo::Fields)
}
#endif // PROCESSINFO_H
@@ -0,0 +1,462 @@
/*
SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "Pty.h"
#include "konsoledebug.h"
// Qt
#include <QStringList>
#include <qplatformdefs.h>
#ifndef Q_OS_WIN
// System
#include <csignal>
#include <errno.h>
#include <sys/ioctl.h> //ioctl() and TIOCSWINSZ
#include <termios.h>
// KDE
#include <KPtyDevice>
using Konsole::Pty;
Pty::Pty(QObject *aParent)
: Pty(-1, aParent)
{
}
Pty::Pty(int masterFd, QObject *aParent)
: KPtyProcess(masterFd, aParent)
{
// Must call parent class child process modifier, as it sets file descriptors ...etc
auto parentChildProcModifier = KPtyProcess::childProcessModifier();
setChildProcessModifier([parentChildProcModifier = std::move(parentChildProcModifier)]() {
if (parentChildProcModifier) {
parentChildProcModifier();
}
// reset all signal handlers
// this ensures that terminal applications respond to
// signals generated via key sequences such as Ctrl+C
// (which sends SIGINT)
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_handler = SIG_DFL;
action.sa_flags = 0;
for (int signal = 1; signal < NSIG; signal++) {
sigaction(signal, &action, nullptr);
}
});
_windowColumns = 0;
_windowLines = 0;
_windowWidth = 0;
_windowHeight = 0;
_eraseChar = 0;
_xonXoff = true;
_utf8 = true;
setEraseChar(_eraseChar);
setFlowControlEnabled(_xonXoff);
setUtf8Mode(_utf8);
setWindowSize(_windowColumns, _windowLines, _windowWidth, _windowHeight);
setUseUtmp(true);
setPtyChannels(KPtyProcess::AllChannels);
connect(pty(), &KPtyDevice::readyRead, this, &Konsole::Pty::dataReceived);
}
Pty::~Pty() = default;
void Pty::sendData(const QByteArray &data)
{
if (data.isEmpty()) {
return;
}
if (pty()->write(data) == -1) {
qCDebug(KonsoleDebug) << "Could not send input data to terminal process.";
return;
}
}
void Pty::dataReceived()
{
QByteArray data = pty()->readAll();
if (data.isEmpty()) {
return;
}
Q_EMIT receivedData(data.constData(), data.length());
}
void Pty::setWindowSize(int columns, int lines, int width, int height)
{
_windowColumns = columns;
_windowLines = lines;
_windowWidth = width;
_windowHeight = height;
if (pty()->masterFd() >= 0) {
pty()->setWinSize(_windowLines, _windowColumns, _windowHeight, _windowWidth);
}
}
QSize Pty::windowSize() const
{
return {_windowColumns, _windowLines};
}
QSize Pty::pixelSize() const
{
return {_windowWidth, _windowHeight};
}
void Pty::setFlowControlEnabled(bool enable)
{
_xonXoff = enable;
if (pty()->masterFd() >= 0) {
struct ::termios ttmode;
pty()->tcGetAttr(&ttmode);
if (enable) {
ttmode.c_iflag |= (IXOFF | IXON);
} else {
ttmode.c_iflag &= ~(IXOFF | IXON);
}
if (!pty()->tcSetAttr(&ttmode)) {
qCDebug(KonsoleDebug) << "Unable to set terminal attributes.";
}
}
}
bool Pty::flowControlEnabled() const
{
if (pty()->masterFd() >= 0) {
struct ::termios ttmode;
pty()->tcGetAttr(&ttmode);
// Check only IXON (flow control on output) on master fd, which corresponds to
// IXOFF (flow control on input) on slave side.
return ((ttmode.c_iflag & IXON) != 0U);
} else {
qCDebug(KonsoleDebug) << "Unable to get flow control status, terminal not connected.";
return _xonXoff;
}
}
void Pty::setUtf8Mode(bool enable)
{
#if defined(IUTF8) // XXX not a reasonable place to check it.
_utf8 = enable;
if (pty()->masterFd() >= 0) {
struct ::termios ttmode;
pty()->tcGetAttr(&ttmode);
if (enable) {
ttmode.c_iflag |= IUTF8;
} else {
ttmode.c_iflag &= ~IUTF8;
}
if (!pty()->tcSetAttr(&ttmode)) {
qCDebug(KonsoleDebug) << "Unable to set terminal attributes.";
}
}
#else
Q_UNUSED(enable)
#endif
}
void Pty::setEraseChar(char eChar)
{
_eraseChar = eChar;
if (pty()->masterFd() >= 0) {
struct ::termios ttmode;
pty()->tcGetAttr(&ttmode);
ttmode.c_cc[VERASE] = eChar;
if (!pty()->tcSetAttr(&ttmode)) {
qCDebug(KonsoleDebug) << "Unable to set terminal attributes.";
}
}
}
char Pty::eraseChar() const
{
if (pty()->masterFd() >= 0) {
struct ::termios ttyAttributes;
pty()->tcGetAttr(&ttyAttributes);
return ttyAttributes.c_cc[VERASE];
} else {
qCDebug(KonsoleDebug) << "Unable to get erase char attribute, terminal not connected.";
return _eraseChar;
}
}
void Pty::setInitialWorkingDirectory(const QString &dir)
{
QString pwd = dir;
// remove trailing slash in the path when appropriate
// example: /usr/share/icons/ ==> /usr/share/icons
if (pwd.length() > 1 && pwd.endsWith(QLatin1Char('/'))) {
pwd.chop(1);
}
setWorkingDirectory(pwd);
// setting PWD to "." will cause problem for bash & zsh
if (pwd != QLatin1String(".")) {
setEnv(QStringLiteral("PWD"), pwd);
}
}
void Pty::addEnvironmentVariables(const QStringList &environmentVariables)
{
bool isTermEnvAdded = false;
for (const QString &pair : environmentVariables) {
// split on the first '=' character
const int separator = pair.indexOf(QLatin1Char('='));
if (separator >= 0) {
QString variable = pair.left(separator);
QString value = pair.mid(separator + 1);
setEnv(variable, value);
if (variable == QLatin1String("TERM")) {
isTermEnvAdded = true;
}
}
}
// extra safeguard to make sure $TERM is always set
if (!isTermEnvAdded) {
setEnv(QStringLiteral("TERM"), QStringLiteral("xterm-256color"));
}
}
int Pty::start(const QString &programName, const QStringList &programArguments, const QStringList &environmentList)
{
clearProgram();
setProgram(programName, programArguments);
addEnvironmentVariables(environmentList);
// unless the LANGUAGE environment variable has been set explicitly
// set it to a null string
// this fixes the problem where KCatalog sets the LANGUAGE environment
// variable during the application's startup to something which
// differs from LANG,LC_* etc. and causes programs run from
// the terminal to display messages in the wrong language
//
// this can happen if LANG contains a language which KDE
// does not have a translation for
//
// BR:149300
setEnv(QStringLiteral("LANGUAGE"), QString(), false /* do not overwrite existing value if any */);
KProcess::start();
if (waitForStarted()) {
return 0;
} else {
return -1;
}
}
void Pty::setWriteable(bool writeable)
{
QT_STATBUF sbuf;
if (QT_STAT(pty()->ttyName(), &sbuf) == 0) {
if (writeable) {
if (::chmod(pty()->ttyName(), sbuf.st_mode | S_IWGRP) < 0) {
qCDebug(KonsoleDebug) << "Could not set writeable on " << pty()->ttyName();
}
} else {
if (::chmod(pty()->ttyName(), sbuf.st_mode & ~(S_IWGRP | S_IWOTH)) < 0) {
qCDebug(KonsoleDebug) << "Could not unset writeable on " << pty()->ttyName();
}
}
} else {
qCDebug(KonsoleDebug) << "Could not stat " << pty()->ttyName();
}
}
void Pty::closePty()
{
pty()->close();
}
int Pty::foregroundProcessGroup() const
{
const int master_fd = pty()->masterFd();
if (master_fd >= 0) {
int foregroundPid = tcgetpgrp(master_fd);
if (foregroundPid != -1) {
return foregroundPid;
} else {
qCWarning(KonsoleDebug, "Failed to get foreground process group id for %d: %s", master_fd, strerror(errno));
return 0;
}
}
qWarning(KonsoleDebug, "foregroundProcessGroup master_fd < 0");
return 0;
}
#else // Windows backend
#include "ptyqt/conptyprocess.h"
using Konsole::Pty;
Pty::Pty(QObject *aParent)
: Pty(-1, aParent)
{
}
Pty::Pty(int masterFd, QObject *aParent)
: QObject(aParent)
{
Q_UNUSED(masterFd)
m_proc = std::make_unique<ConPtyProcess>();
if (!m_proc->isAvailable()) {
m_proc.reset();
}
_windowColumns = 0;
_windowLines = 0;
_windowWidth = 0;
_windowHeight = 0;
_eraseChar = 0;
_xonXoff = true;
_utf8 = true;
setEraseChar(_eraseChar);
}
Pty::~Pty() = default;
void Pty::sendData(const QByteArray &data)
{
if (m_proc) {
m_proc->write(data.constData(), data.length());
}
}
void Pty::dataReceived()
{
if (m_proc) {
auto data = m_proc->readAll();
Q_EMIT receivedData(data.constData(), data.length());
}
}
void Pty::setWindowSize(int columns, int lines, int, int)
{
if (m_proc && isRunning())
m_proc->resize(columns, lines);
}
QSize Pty::windowSize() const
{
if (!m_proc) {
return {};
}
auto s = m_proc->size();
return QSize(s.first, s.second);
}
QSize Pty::pixelSize() const
{
return QSize();
}
void Pty::setFlowControlEnabled(bool enable)
{
_xonXoff = enable;
}
bool Pty::flowControlEnabled() const
{
return false;
}
void Pty::setUtf8Mode(bool)
{
}
void Pty::setEraseChar(char eChar)
{
_eraseChar = eChar;
}
char Pty::eraseChar() const
{
return _eraseChar;
}
void Pty::setInitialWorkingDirectory(const QString & /*dir*/)
{
}
void Pty::addEnvironmentVariables(const QStringList & /*environmentVariables*/)
{
}
int Pty::start(const QString &program, const QStringList &arguments, const QString &workingDir, const QStringList &environment, int cols, int lines)
{
if (!m_proc || !m_proc->isAvailable()) {
return -1;
}
bool res = m_proc->startProcess(program, arguments, workingDir, environment, cols, lines);
if (!res) {
return -1;
} else {
auto n = m_proc->notifier();
connect(n, &QIODevice::readyRead, this, &Pty::dataReceived);
connect(m_proc.get(), &IPtyProcess::exited, this, [this] {
Q_EMIT finished(exitCode(), QProcess::NormalExit);
});
connect(n, &QIODevice::aboutToClose, this, [this] {
Q_EMIT finished(exitCode(), QProcess::NormalExit);
});
}
return 0;
}
void Pty::setWriteable(bool)
{
}
void Pty::closePty()
{
if (m_proc) {
m_proc->kill();
}
}
int Pty::foregroundProcessGroup() const
{
return 0;
}
#endif
#include "moc_Pty.cpp"
+248
View File
@@ -0,0 +1,248 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef PTY_H
#define PTY_H
// Qt
#include <QProcess>
#include <QSize>
// Konsole
#include "konsoleprivate_export.h"
#ifndef Q_OS_WIN
// KDE
#include <KPtyProcess>
#else
#include <QObject>
#include "ptyqt/iptyprocess.h"
#endif
namespace Konsole
{
/**
* The Pty class is used to start the terminal process,
* send data to it, receive data from it and manipulate
* various properties of the pseudo-teletype interface
* used to communicate with the process.
*
* To use this class, construct an instance and connect
* to the sendData slot and receivedData signal to
* send data to or receive data from the process.
*
* To start the terminal process, call the start() method
* with the program name and appropriate arguments.
*/
#ifdef Q_OS_WIN
#define ParentClass QObject
#else
#define ParentClass KPtyProcess
#endif
class KONSOLEPRIVATE_EXPORT Pty : public ParentClass
{
Q_OBJECT
public:
/**
* Constructs a new Pty.
*
* Connect to the sendData() slot and receivedData() signal to prepare
* for sending and receiving data from the terminal process.
*
* To start the terminal process, call the run() method with the
* name of the program to start and appropriate arguments.
*/
explicit Pty(QObject *parent = nullptr);
/**
* Construct a process using an open pty master.
* See KPtyProcess::KPtyProcess()
*/
explicit Pty(int ptyMasterFd, QObject *parent = nullptr);
~Pty() override;
#ifndef Q_OS_WIN
/**
* Starts the terminal process.
*
* Returns 0 if the process was started successfully or non-zero
* otherwise.
*
* @param program Path to the program to start
* @param arguments Arguments to pass to the program being started
* @param environment A list of key=value pairs which will be added
* to the environment for the new process. At the very least this
* should include an assignment for the TERM environment variable.
*/
int start(const QString &program, const QStringList &arguments, const QStringList &environment);
#else
/**
* Starts the terminal process.
*
* Returns 0 if the process was started successfully or non-zero
* otherwise.
*
* @param program Path to the program to start
* @param arguments Arguments to pass to the program being started
* @param workingDir initial working directory
* @param environment A list of key=value pairs which will be added
* to the environment for the new process. At the very least this
* should include an assignment for the TERM environment variable.
*/
int start(const QString &program, const QStringList &arguments, const QString &workingDir, const QStringList &environment, int cols, int lines);
#endif
/** Control whether the pty device is writeable by group members. */
void setWriteable(bool writeable);
/**
* Enables or disables Xon/Xoff flow control. The flow control setting
* may be changed later by a terminal application, so flowControlEnabled()
* may not equal the value of @p on in the previous call to setFlowControlEnabled()
*/
void setFlowControlEnabled(bool on);
/** Queries the terminal state and returns true if Xon/Xoff flow control is enabled. */
bool flowControlEnabled() const;
/**
* Sets the size of the window (in columns and lines of characters,
* and width and height in pixels) used by this teletype.
*/
void setWindowSize(int columns, int lines, int width, int height);
/** Returns the size of the window used by this teletype in characters. See setWindowSize() */
QSize windowSize() const;
/** Returns the size of the window used by this teletype in pixels. See setWindowSize() */
QSize pixelSize() const;
/**
* Sets the special character for erasing previous not-yet-erased character.
* See termios(3) for detailed description.
*/
void setEraseChar(char eraseChar);
/** */
char eraseChar() const;
/**
* Sets the initial working directory.
*/
void setInitialWorkingDirectory(const QString &dir);
/**
* Returns the process id of the teletype's current foreground
* process. This is the process which is currently reading
* input sent to the terminal via. sendData()
*
* If there is a problem reading the foreground process group,
* 0 will be returned.
*/
int foregroundProcessGroup() const;
/**
* Close the underlying pty master/slave pair.
*/
void closePty();
#ifdef Q_OS_WIN
int processId() const
{
if (m_proc && m_proc->isAvailable()) {
return m_proc->pid();
}
return 0;
}
bool isRunning() const
{
return processId() > 0;
}
QString errorString() const
{
if (m_proc) {
return m_proc->lastError();
}
return QStringLiteral("Conhost failed to start");
}
bool kill()
{
if (m_proc) {
return m_proc->kill();
}
return false;
}
int exitCode() const
{
if (m_proc) {
return m_proc->exitCode();
}
return -1;
}
Q_SIGNAL void finished(int exitCode, QProcess::ExitStatus);
#endif
public Q_SLOTS:
/**
* Put the pty into UTF-8 mode on systems which support it.
*/
void setUtf8Mode(bool on);
/**
* Sends data to the process currently controlling the
* teletype ( whose id is returned by foregroundProcessGroup() )
*
* @param data the data to send.
*/
void sendData(const QByteArray &data);
Q_SIGNALS:
/**
* Emitted when a new block of data is received from
* the teletype.
*
* @param buffer Pointer to the data received.
* @param length Length of @p buffer
*/
void receivedData(const char *buffer, int length);
private Q_SLOTS:
// called when data is received from the terminal process
void dataReceived();
private:
void init();
// takes a list of key=value pairs and adds them
// to the environment for the process
void addEnvironmentVariables(const QStringList &environment);
int _windowColumns;
int _windowLines;
int _windowWidth;
int _windowHeight;
char _eraseChar;
bool _xonXoff;
bool _utf8;
#ifdef Q_OS_WIN
std::unique_ptr<IPtyProcess> m_proc;
#endif
};
}
#undef ParentClass
#endif // PTY_H
@@ -0,0 +1,88 @@
/*
SPDX-FileCopyrightText: 2010 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "RenameTabDialog.h"
// Konsole
#include "Shortcut_p.h"
#include "ui_RenameTabDialog.h"
#include <KLocalizedString>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
using Konsole::RenameTabDialog;
RenameTabDialog::RenameTabDialog(QWidget *parent)
: QDialog(parent)
, _ui(nullptr)
{
setWindowTitle(i18n("Tab Properties"));
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto mainWidget = new QWidget(this);
auto mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setAutoDefault(true);
connect(buttonBox, &QDialogButtonBox::accepted, this, &RenameTabDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &RenameTabDialog::reject);
mainLayout->addWidget(buttonBox);
setWindowModality(Qt::WindowModal);
_ui = new Ui::RenameTabDialog();
_ui->setupUi(mainWidget);
}
RenameTabDialog::~RenameTabDialog()
{
delete _ui;
}
void RenameTabDialog::focusTabTitleText()
{
_ui->renameTabWidget->focusTabTitleText();
}
void RenameTabDialog::focusRemoteTabTitleText()
{
_ui->renameTabWidget->focusRemoteTabTitleText();
}
void RenameTabDialog::setTabTitleText(const QString &text)
{
_ui->renameTabWidget->setTabTitleText(text);
}
void RenameTabDialog::setRemoteTabTitleText(const QString &text)
{
_ui->renameTabWidget->setRemoteTabTitleText(text);
}
void RenameTabDialog::setColor(const QColor &color)
{
_ui->renameTabWidget->setColor(color);
}
QString RenameTabDialog::tabTitleText() const
{
return _ui->renameTabWidget->tabTitleText();
}
QString RenameTabDialog::remoteTabTitleText() const
{
return _ui->renameTabWidget->remoteTabTitleText();
}
QColor RenameTabDialog::color() const
{
return _ui->renameTabWidget->color();
}
#include "moc_RenameTabDialog.cpp"
@@ -0,0 +1,46 @@
/*
SPDX-FileCopyrightText: 2010 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef RENAMETABDIALOG_H
#define RENAMETABDIALOG_H
// KDE
#include "konsoleprivate_export.h"
#include <QDialog>
namespace Ui
{
class RenameTabDialog;
}
namespace Konsole
{
class KONSOLEPRIVATE_EXPORT RenameTabDialog : public QDialog
{
Q_OBJECT
public:
explicit RenameTabDialog(QWidget *parent = nullptr);
~RenameTabDialog() override;
QString tabTitleText() const;
QString remoteTabTitleText() const;
QColor color() const;
void setTabTitleText(const QString &);
void setRemoteTabTitleText(const QString &);
void setColor(const QColor &);
void focusTabTitleText();
void focusRemoteTabTitleText();
private:
Q_DISABLE_COPY(RenameTabDialog)
Ui::RenameTabDialog *_ui;
};
}
#endif
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RenameTabDialog</class>
<widget class="QWidget" name="RenameTabDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>325</width>
<height>110</height>
</rect>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="Konsole::RenameTabWidget" name="renameTabWidget" native="true"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Konsole::RenameTabWidget</class>
<extends>QWidget</extends>
<header>widgets/RenameTabWidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,206 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "SSHProcessInfo.h"
// Unix
#ifndef Q_OS_WIN
#include <arpa/inet.h>
#include <cerrno>
#include <netinet/in.h>
#include <pwd.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <unistd.h>
#endif // Q_OS_WIN
// Qt
#include <QDebug>
using namespace Konsole;
SSHProcessInfo::SSHProcessInfo(const ProcessInfo &process)
: _process(process)
, _user(QString())
, _host(QString())
, _port(QString())
, _command(QString())
{
bool ok = false;
// check that this is a SSH process
const QString &name = _process.name(&ok);
if (!ok || name != QLatin1String("ssh")) {
if (!ok) {
qWarning() << "Could not read process info";
} else {
qWarning() << "Process is not a SSH process";
}
return;
}
// read arguments
const QVector<QString> &args = _process.arguments(&ok);
// SSH options
// these are taken from the SSH manual ( accessed via 'man ssh' )
// options which take no arguments
static const QString noArgumentOptions(QStringLiteral("1246AaCfgKkMNnqsTtVvXxYy"));
// options which take one argument
static const QString singleArgumentOptions(QStringLiteral("bcDeFIiJLlmOopRSWw"));
if (ok) {
// find the username, host and command arguments
//
// the username/host is assumed to be the first argument
// which is not an option
// ( ie. does not start with a dash '-' character )
// or an argument to a previous option.
//
// the command, if specified, is assumed to be the argument following
// the username and host
//
// note that we skip the argument at index 0 because that is the
// program name ( expected to be 'ssh' in this case )
for (int i = 1; i < args.count(); i++) {
// If this one is an option ...
// Most options together with its argument will be skipped.
if (args[i].startsWith(QLatin1Char('-'))) {
const QChar optionChar = (args[i].length() > 1) ? args[i][1] : QLatin1Char('\0');
// for example: -p2222 or -p 2222 ?
const bool optionArgumentCombined = args[i].length() > 2;
if (noArgumentOptions.contains(optionChar)) {
continue;
} else if (singleArgumentOptions.contains(optionChar)) {
QString argument;
if (optionArgumentCombined) {
argument = args[i].mid(2);
} else {
// Verify correct # arguments are given
if ((i + 1) < args.count()) {
argument = args[i + 1];
}
i++;
}
// support using `-l user` to specify username.
if (optionChar == QLatin1Char('l')) {
_user = argument;
}
// support using `-p port` to specify port.
else if (optionChar == QLatin1Char('p')) {
_port = argument;
}
continue;
}
}
// check whether the host has been found yet
// if not, this must be the username/host argument
if (_host.isEmpty()) {
// check to see if only a hostname is specified, or whether
// both a username and host are specified ( in which case they
// are separated by an '@' character: username@host )
const int separatorPosition = args[i].indexOf(QLatin1Char('@'));
if (separatorPosition != -1) {
// username and host specified
_user = args[i].left(separatorPosition);
_host = args[i].mid(separatorPosition + 1);
} else {
// just the host specified
_host = args[i];
}
} else {
// host has already been found, this must be part of the
// command arguments.
// Note this is not 100% correct. If any of the above
// noArgumentOptions or singleArgumentOptions are found, this
// will not be correct (example ssh server top -i 50)
// Suggest putting ssh command in quotes
if (_command.isEmpty()) {
_command = args[i];
} else {
_command = _command + QLatin1Char(' ') + args[i];
}
}
}
} else {
qWarning() << "Could not read arguments";
return;
}
}
QString SSHProcessInfo::userName() const
{
return _user;
}
QString SSHProcessInfo::host() const
{
return _host;
}
QString SSHProcessInfo::port() const
{
return _port;
}
QString SSHProcessInfo::command() const
{
return _command;
}
QString SSHProcessInfo::format(const QString &input) const
{
QString output(input);
// search for and replace known markers
output.replace(QLatin1String("%u"), _user);
// provide 'user@' if user is defined -- this makes nicer
// remote tabs possible: "%U%h %c" => User@Host Command
// => Host Command
// Depending on whether -l was passed to ssh (which is mostly not the
// case due to ~/.ssh/config).
if (_user.isEmpty()) {
output.remove(QLatin1String("%U"));
} else {
output.replace(QLatin1String("%U"), _user + QLatin1Char('@'));
}
#ifdef Q_OS_WIN
// TODO
#else
// test whether host is an ip address
// in which case 'short host' and 'full host'
// markers in the input string are replaced with
// the full address
struct in_addr address;
const bool isIpAddress = inet_aton(_host.toLocal8Bit().constData(), &address) != 0;
if (isIpAddress) {
output.replace(QLatin1String("%h"), _host);
} else {
output.replace(QLatin1String("%h"), _host.left(_host.indexOf(QLatin1Char('.'))));
}
QString fullHost = _host;
if (!_port.isEmpty() && _port != QLatin1String("22")) {
fullHost.append(QLatin1Char(':'));
fullHost.append(_port);
}
output.replace(QLatin1String("%H"), fullHost);
output.replace(QLatin1String("%c"), _command);
#endif
return output;
}
@@ -0,0 +1,75 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SSHPROCESSINFO_H
#define SSHPROCESSINFO_H
#include "ProcessInfo.h"
namespace Konsole
{
/**
* Lightweight class which provides additional information about SSH processes.
*/
class KONSOLEPRIVATE_EXPORT SSHProcessInfo
{
public:
/**
* Constructs a new SSHProcessInfo instance which provides additional
* information about the specified SSH process.
*
* @param process A ProcessInfo instance for a SSH process.
*/
explicit SSHProcessInfo(const ProcessInfo &process);
/**
* Returns the user name which the user initially logged into on
* the remote computer.
*/
QString userName() const;
/**
* Returns the host which the user has connected to.
*/
QString host() const;
/**
* Returns the port on host which the user has connected to.
*/
QString port() const;
/**
* Returns the command which the user specified to execute on the
* remote computer when starting the SSH process.
*/
QString command() const;
/**
* Operates in the same way as ProcessInfo::format(), except
* that the set of markers understood is different:
*
* %u - Replaced with user name which the user initially logged
* into on the remote computer.
* %h - Replaced with the first part of the host name which
* is connected to.
* %H - Replaced with the full host name of the computer which
* is connected to.
* %c - Replaced with the command which the user specified
* to execute when starting the SSH process.
*/
QString format(const QString &input) const;
private:
const ProcessInfo &_process;
QString _user;
QString _host;
QString _port;
QString _command;
};
}
#endif
@@ -0,0 +1,192 @@
/*
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 2009 Thomas Dreibholz <dreibh@iem.uni-due.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "SaveHistoryTask.h"
#include <QApplication>
#include <QFileDialog>
#include <QTextStream>
#include <KConfig>
#include <KConfigGroup>
#include <KIO/TransferJob>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSharedConfig>
#include "Emulation.h"
#include "session/SessionManager.h"
#include "../decoders/HTMLDecoder.h"
#include "../decoders/PlainTextDecoder.h"
#include "colorscheme/ColorScheme.h"
#include "colorscheme/ColorSchemeManager.h"
namespace Konsole
{
QString SaveHistoryTask::_saveDialogRecentURL;
SaveHistoryTask::SaveHistoryTask(QObject *parent)
: SessionTask(parent)
{
}
SaveHistoryTask::~SaveHistoryTask() = default;
void SaveHistoryTask::execute()
{
// TODO - think about the UI when saving multiple history sessions, if there are more than two or
// three then providing a URL for each one will be tedious
// TODO - show a warning ( preferably passive ) if saving the history output fails
QFileDialog *dialog = new QFileDialog(QApplication::activeWindow());
dialog->setAcceptMode(QFileDialog::AcceptSave);
QStringList mimeTypes{QStringLiteral("text/plain"), QStringLiteral("text/html")};
dialog->setMimeTypeFilters(mimeTypes);
KSharedConfigPtr konsoleConfig = KSharedConfig::openConfig();
auto group = konsoleConfig->group(QStringLiteral("SaveHistory Settings"));
if (_saveDialogRecentURL.isEmpty()) {
const auto list = group.readPathEntry("Recent URLs", QStringList());
if (list.isEmpty()) {
dialog->setDirectory(QDir::homePath());
} else {
dialog->setDirectoryUrl(QUrl(list.at(0)));
}
} else {
dialog->setDirectoryUrl(QUrl(_saveDialogRecentURL));
}
// iterate over each session in the task and display a dialog to allow the user to choose where
// to save that session's history.
// then start a KIO job to transfer the data from the history to the chosen URL
const QList<QPointer<Session>> sessionsList = sessions();
for (const auto &session : sessionsList) {
dialog->setWindowTitle(i18n("Save Output From %1", session->title(Session::NameRole)));
int result = dialog->exec();
if (result != QDialog::Accepted) {
continue;
}
QUrl url = (dialog->selectedUrls()).at(0);
if (!url.isValid()) {
// UI: Can we make this friendlier?
KMessageBox::error(nullptr, i18n("%1 is an invalid URL, the output could not be saved.", url.url()));
continue;
}
// Save selected URL for next time
_saveDialogRecentURL = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toString();
group.writePathEntry("Recent URLs", _saveDialogRecentURL);
KIO::TransferJob *job = KIO::put(url,
-1, // no special permissions
// overwrite existing files
// do not resume an existing transfer
// show progress information only for remote
// URLs
KIO::Overwrite | (url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags)
// a better solution would be to show progress
// information after a certain period of time
// instead, since the overall speed of transfer
// depends on factors other than just the protocol
// used
);
SaveJob jobInfo;
jobInfo.session = session;
jobInfo.lastLineFetched = -1; // when each request for data comes in from the KIO subsystem
// lastLineFetched is used to keep track of how much of the history
// has already been sent, and where the next request should continue
// from.
// this is set to -1 to indicate the job has just been started
if (((dialog->selectedNameFilter()).contains(QLatin1String("html"), Qt::CaseInsensitive))
|| ((dialog->selectedFiles()).at(0).endsWith(QLatin1String("html"), Qt::CaseInsensitive))) {
Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
const auto schemeName = profile->colorScheme();
const auto scheme = ColorSchemeManager::instance()->findColorScheme(schemeName);
QColor colorTable[TABLE_COLORS];
if (scheme) {
scheme->getColorTable(colorTable);
} else {
std::copy_n(ColorScheme::defaultTable, TABLE_COLORS, colorTable);
}
jobInfo.decoder = new HTMLDecoder(colorTable, profile->font());
} else {
jobInfo.decoder = new PlainTextDecoder();
}
_jobSession.insert(job, jobInfo);
connect(job, &KIO::TransferJob::dataReq, this, &Konsole::SaveHistoryTask::jobDataRequested);
connect(job, &KIO::TransferJob::result, this, &Konsole::SaveHistoryTask::jobResult);
}
dialog->deleteLater();
}
void SaveHistoryTask::jobDataRequested(KIO::Job *job, QByteArray &data)
{
// TODO - Report progress information for the job
// PERFORMANCE: Do some tests and tweak this value to get faster saving
const int LINES_PER_REQUEST = 500;
SaveJob &info = _jobSession[job];
// transfer LINES_PER_REQUEST lines from the session's history
// to the save location
if (!info.session.isNull()) {
// note: when retrieving lines from the emulation,
// the first line is at index 0.
int sessionLines = info.session->emulation()->lineCount();
if (sessionLines - 1 == info.lastLineFetched) {
return; // if there is no more data to transfer then stop the job
}
int copyUpToLine = qMin(info.lastLineFetched + LINES_PER_REQUEST, sessionLines - 1);
QTextStream stream(&data, QIODevice::ReadWrite);
info.decoder->begin(&stream);
info.session->emulation()->writeToStream(info.decoder, info.lastLineFetched + 1, copyUpToLine);
info.decoder->end();
info.lastLineFetched = copyUpToLine;
}
}
void SaveHistoryTask::jobResult(KJob *job)
{
if (job->error() != 0) {
KMessageBox::error(nullptr, i18n("A problem occurred when saving the output.\n%1", job->errorString()));
}
TerminalCharacterDecoder *decoder = _jobSession[job].decoder;
_jobSession.remove(job);
delete decoder;
// notify the world that the task is done
Q_EMIT completed(true);
if (autoDelete()) {
deleteLater();
}
}
}
#include "moc_SaveHistoryTask.cpp"
@@ -0,0 +1,65 @@
/*
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 2009 Thomas Dreibholz <dreibh@iem.uni-due.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SAVEHISTORYTASK_H
#define SAVEHISTORYTASK_H
#include "konsoleprivate_export.h"
#include "session/SessionTask.h"
#include <kio/job.h>
namespace Konsole
{
class TerminalCharacterDecoder;
/**
* A task which prompts for a URL for each session and saves that session's output
* to the given URL
*/
class KONSOLEPRIVATE_EXPORT SaveHistoryTask : public SessionTask
{
Q_OBJECT
public:
/** Constructs a new task to save session output to URLs */
explicit SaveHistoryTask(QObject *parent = nullptr);
~SaveHistoryTask() override;
/**
* Opens a save file dialog for each session in the group and begins saving
* each session's history to the given URL.
*
* The data transfer is performed asynchronously and will continue after execute() returns.
*/
void execute() override;
private Q_SLOTS:
void jobDataRequested(KIO::Job *job, QByteArray &data);
void jobResult(KJob *job);
private:
class SaveJob // structure to keep information needed to process
// incoming data requests from jobs
{
public:
QPointer<Session> session; // the session associated with a history save job
int lastLineFetched; // the last line processed in the previous data request
// set this to -1 at the start of the save job
TerminalCharacterDecoder *decoder; // decoder used to convert terminal characters
// into output
};
QHash<KJob *, SaveJob> _jobSession;
static QString _saveDialogRecentURL;
};
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,914 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SCREEN_H
#define SCREEN_H
// STD
#include <memory>
// Qt
#include <QBitArray>
#include <QRect>
#include <QSet>
#include <QVarLengthArray>
#include <QVector>
// Konsole
#include "../characters/Character.h"
#include "konsoleprivate_export.h"
#define MODE_Origin 0
#define MODE_Wrap 1
#define MODE_Insert 2
#define MODE_Screen 3
#define MODE_Cursor 4
#define MODE_NewLine 5
#define MODE_AppScreen 6
#define MODE_SelectCursor 7
#define MODES_SCREEN 8
#define REPL_None 0
#define REPL_PROMPT 1
#define REPL_INPUT 2
#define REPL_OUTPUT 3
struct TerminalGraphicsPlacement_t {
QPixmap pixmap;
qint64 id;
qint64 pid;
int z, X, Y, col, row, cols, rows;
qreal opacity;
bool scrolling;
};
namespace Konsole
{
class TerminalCharacterDecoder;
class TerminalDisplay;
class HistoryType;
class HistoryScroll;
class EscapeSequenceUrlExtractor;
/**
\brief An image of characters with associated attributes.
The terminal emulation ( Emulation ) receives a serial stream of
characters from the program currently running in the terminal.
From this stream it creates an image of characters which is ultimately
rendered by the display widget ( TerminalDisplay ). Some types of emulation
may have more than one screen image.
getImage() is used to retrieve the currently visible image
which is then used by the display widget to draw the output from the
terminal.
The number of lines of output history which are kept in addition to the current
screen image depends on the history scroll being used to store the output.
The scroll is specified using setScroll()
The output history can be retrieved using writeToStream()
The screen image has a selection associated with it, specified using
setSelectionStart() and setSelectionEnd(). The selected text can be retrieved
using selectedText(). When getImage() is used to retrieve the visible image,
characters which are part of the selection have their colors inverted.
*/
class KONSOLEPRIVATE_EXPORT Screen
{
public:
/* PlainText: Return plain text (default)
* ConvertToHtml: Specifies if returned text should have HTML tags.
* PreserveLineBreaks: Specifies whether new line characters should be
* inserted into the returned text at the end of each terminal line.
* TrimLeadingWhitespace: Specifies whether leading spaces should be
* trimmed in the returned text.
* TrimTrailingWhitespace: Specifies whether trailing spaces should be
* trimmed in the returned text.
*/
enum DecodingOption {
PlainText = 0x0,
ConvertToHtml = 0x1,
PreserveLineBreaks = 0x2,
TrimLeadingWhitespace = 0x4,
TrimTrailingWhitespace = 0x8,
ExcludePrompt = 0x10,
ExcludeInput = 0x20,
ExcludeOutput = 0x40,
};
Q_DECLARE_FLAGS(DecodingOptions, DecodingOption)
/** Construct a new screen image of size @p lines by @p columns. */
Screen(int lines, int columns);
~Screen();
Screen(const Screen &) = delete;
Screen &operator=(const Screen &) = delete;
EscapeSequenceUrlExtractor *urlExtractor() const;
// VT100/2 Operations
// Cursor Movement
/**
* Move the cursor up by @p n lines. The cursor will stop at the
* top margin.
*/
void cursorUp(int n);
/**
* Move the cursor down by @p n lines. The cursor will stop at the
* bottom margin.
*/
void cursorDown(int n);
/**
* Move the cursor to the left by @p n columns.
* The cursor will stop at the first column.
*/
void cursorLeft(int n);
/**
* Moves cursor to beginning of the line by @p n lines down.
* The cursor will stop at the beginning of the line.
*/
void cursorNextLine(int n);
/**
* Moves cursor to beginning of the line by @p n lines up.
* The cursor will stop at the beginning of the line.
*/
void cursorPreviousLine(int n);
/**
* Move the cursor to the right by @p n columns.
* The cursor will stop at the right-most column.
*/
void cursorRight(int n);
/** Position the cursor on line @p y. */
void setCursorY(int y);
/** Position the cursor at column @p x. */
void setCursorX(int x);
/** Position the cursor at line @p y, column @p x. */
void setCursorYX(int y, int x);
void initSelCursor();
int selCursorUp(int n);
int selCursorDown(int n);
int selCursorLeft(int n);
int selCursorRight(int n);
int selSetSelectionStart(int mode);
int selSetSelectionEnd(int mode);
/**
* Sets the margins for scrolling the screen.
*
* @param top The top line of the new scrolling margin.
* @param bot The bottom line of the new scrolling margin.
*/
void setMargins(int top, int bot);
/** Returns the top line of the scrolling region. */
int topMargin() const;
/** Returns the bottom line of the scrolling region. */
int bottomMargin() const;
/**
* Resets the scrolling margins back to the top and bottom lines
* of the screen.
*/
void setDefaultMargins();
/**
* Moves the cursor down one line, if the MODE_NewLine mode
* flag is enabled then the cursor is returned to the leftmost
* column first.
*
* Equivalent to NextLine() if the MODE_NewLine flag is set
* or index() otherwise.
*/
void newLine();
/**
* Moves the cursor down one line and positions it at the beginning
* of the line. Equivalent to calling Return() followed by index()
*/
void nextLine();
/**
* Move the cursor down one line. If the cursor is on the bottom
* line of the scrolling region (as returned by bottomMargin()) the
* scrolling region is scrolled up by one line instead.
*/
void index();
/**
* Move the cursor up one line. If the cursor is on the top line
* of the scrolling region (as returned by topMargin()) the scrolling
* region is scrolled down by one line instead.
*/
void reverseIndex();
/**
* Scroll the scrolling region of the screen up by @p n lines.
* The scrolling region is initially the whole screen, but can be changed
* using setMargins()
*/
void scrollUp(int n);
/**
* Scroll the scrolling region of the screen down by @p n lines.
* The scrolling region is initially the whole screen, but can be changed
* using setMargins()
*/
void scrollDown(int n);
/**
* Moves the cursor to the beginning of the current line.
* Equivalent to setCursorX(0)
*/
void toStartOfLine();
/**
* Moves the cursor one column to the left and erases the character
* at the new cursor position.
*/
void backspace();
/** Moves the cursor @p n tab-stops to the right. */
void tab(int n = 1);
/** Moves the cursor @p n tab-stops to the left. */
void backtab(int n);
// Editing
/**
* Erase @p n characters beginning from the current cursor position.
* This is equivalent to over-writing @p n characters starting with the current
* cursor position with spaces.
* If @p n is 0 then one character is erased.
*/
void eraseChars(int n);
/**
* Delete @p n characters beginning from the current cursor position.
* If @p n is 0 then one character is deleted.
*/
void deleteChars(int n);
/**
* Insert @p n blank characters beginning from the current cursor position.
* The position of the cursor is not altered.
* If @p n is 0 then one character is inserted.
*/
void insertChars(int n);
/**
* Repeat the preceding graphic character @p n times, including SPACE.
* If @p n is 0 then the character is repeated once.
*/
void repeatChars(int n);
/**
* Removes @p n lines beginning from the current cursor position.
* The position of the cursor is not altered.
* If @p n is 0 then one line is removed.
*/
void deleteLines(int n);
/**
* Inserts @p lines beginning from the current cursor position.
* The position of the cursor is not altered.
* If @p n is 0 then one line is inserted.
*/
void insertLines(int n);
/** Clears all the tab stops. */
void clearTabStops();
/** Sets or removes a tab stop at the cursor's current column. */
void changeTabStop(bool set);
/** Resets (clears) the specified screen @p m. */
void resetMode(int m);
/** Sets (enables) the specified screen @p m. */
void setMode(int m);
/**
* Saves the state of the specified screen @p m. It can be restored
* using restoreMode()
*/
void saveMode(int m);
/** Restores the state of a screen @p m saved by calling saveMode() */
void restoreMode(int m);
/** Returns whether the specified screen @p me is enabled or not .*/
bool getMode(int m) const;
/**
* Saves the current position and appearance (text color and style) of the cursor.
* It can be restored by calling restoreCursor()
*/
void saveCursor();
/** Restores the position and appearance of the cursor. See saveCursor() */
void restoreCursor();
/** Clear the whole screen, moving the current screen contents into the history first. */
void clearEntireScreen();
/**
* Clear the area of the screen from the current cursor position to the end of
* the screen.
*/
void clearToEndOfScreen();
/**
* Clear the area of the screen from the current cursor position to the start
* of the screen.
*/
void clearToBeginOfScreen();
/** Clears the whole of the line on which the cursor is currently positioned. */
void clearEntireLine();
/** Clears from the current cursor position to the end of the line. */
void clearToEndOfLine();
/** Clears from the current cursor position to the beginning of the line. */
void clearToBeginOfLine();
/** Fills the entire screen with the letter 'E' */
void helpAlign();
/**
* Enables the given @p rendition flag. Rendition flags control the appearance
* of characters on the screen.
*
* @see Character::rendition
*/
void setRendition(RenditionFlags rendition);
void setUnderlineType(int type);
/**
* Disables the given @p rendition flag. Rendition flags control the appearance
* of characters on the screen.
*
* @see Character::rendition
*/
void resetRendition(RenditionFlags rendition);
/**
* Sets the cursor's foreground color.
* @param space The color space used by the @p color argument
* @param color The new foreground color. The meaning of this depends on
* the color @p space used.
*
* @see CharacterColor
*/
void setForeColor(int space, int color);
/**
* Sets the cursor's background color.
* @param space The color space used by the @p color argument.
* @param color The new background color. The meaning of this depends on
* the color @p space used.
*
* @see CharacterColor
*/
void setBackColor(int space, int color);
/**
* Resets the cursor's color back to the default and sets the
* character's rendition flags back to the default settings.
*/
void setDefaultRendition();
void setULColor(int space, int color);
CharacterColor const *ulColorTable() const
{
return _ulColors;
}
/** Returns the column which the cursor is positioned at. */
int getCursorX() const;
/** Returns the line which the cursor is positioned on. */
int getCursorY() const;
/**
* Resets the state of the screen. This resets the various screen modes
* back to their default states. The cursor style and colors are reset
* (as if setDefaultRendition() had been called)
*
* <ul>
* <li>Line wrapping is enabled.</li>
* <li>Origin mode is disabled.</li>
* <li>Insert mode is disabled.</li>
* <li>Cursor mode is enabled. TODO Document me</li>
* <li>Screen mode is disabled. TODO Document me</li>
* <li>New line mode is disabled. TODO Document me</li>
* </ul>
*
* If @p softReset is true then perform a DECSTR,
* otherwise perform RIS (Reset to Initial State).
*
* If @p preservePrompt is true, then attempt to preserve the
* line with the command prompt even on a RIS.
*/
void reset(bool softReset = false, bool preservePrompt = false);
/**
* Displays a new character at the current cursor position.
*
* If the cursor is currently positioned at the right-edge of the screen and
* line wrapping is enabled then the character is added at the start of a new
* line below the current one.
*
* If the MODE_Insert screen mode is currently enabled then the character
* is inserted at the current cursor position, otherwise it will replace the
* character already at the current cursor position.
*/
void displayCharacter(uint c);
/**
* Resizes the image to a new fixed size of @p new_lines by @p new_columns.
* In the case that @p new_columns is smaller than the current number of columns,
* existing lines are not truncated. This prevents characters from being lost
* if the terminal display is resized smaller and then larger again.
*
* The top and bottom margins are reset to the top and bottom of the new
* screen size. Tab stops are also reset and the current selection is
* cleared.
*/
void resizeImage(int new_lines, int new_columns);
/**
* Returns the current screen image.
* The result is an array of Characters of size [getLines()][getColumns()] which
* must be freed by the caller after use.
*
* @param dest Buffer to copy the characters into
* @param size Size of @p dest in Characters
* @param startLine Index of first line to copy
* @param endLine Index of last line to copy
*/
void getImage(Character *dest, int size, int startLine, int endLine) const;
/**
* Returns the additional attributes associated with lines in the image.
* The most important attribute is LINE_WRAPPED which specifies that the
* line is wrapped,
* other attributes control the size of characters in the line.
*/
QVector<LineProperty> getLineProperties(int startLine, int endLine) const;
/** Return the number of lines. */
int getLines() const
{
return _lines;
}
/** Return the number of columns. */
int getColumns() const
{
return _columns;
}
/** Return the number of lines in the history buffer. */
int getHistLines() const;
/**
* Sets the type of storage used to keep lines in the history.
* If @p copyPreviousScroll is true then the contents of the previous
* history buffer are copied into the new scroll.
*/
void setScroll(const HistoryType &, bool copyPreviousScroll = true);
/** Returns the type of storage used to keep lines in the history. */
const HistoryType &getScroll() const;
/**
* Returns true if this screen keeps lines that are scrolled off the screen
* in a history buffer.
*/
bool hasScroll() const;
/**
* Sets the start of the selection.
*
* @param x The column index of the first character in the selection.
* @param y The line index of the first character in the selection.
* @param blockSelectionMode True if the selection is in column mode.
*/
void setSelectionStart(const int x, const int y, const bool blockSelectionMode);
/**
* Sets the end of the current selection.
*
* @param x The column index of the last character in the selection.
* @param y The line index of the last character in the selection.
* @param trimTrailingWhitespace True if trailing whitespace is trimmed from the selection
*/
void setSelectionEnd(const int x, const int y, const bool trimTrailingWhitespace);
/**
* Selects a range of characters with the same REPL mode as the character at (@p x, @p y)
*/
void selectReplContigious(const int x, const int y);
/**
* Retrieves the start of the selection or the cursor position if there
* is no selection.
*/
void getSelectionStart(int &column, int &line) const;
/**
* Retrieves the end of the selection or the cursor position if there
* is no selection.
*/
void getSelectionEnd(int &column, int &line) const;
/** Clears the current selection */
void clearSelection();
/** Return the selection state */
bool hasSelection() const;
/**
* Returns true if the character at (@p x, @p y) is part of the
* current selection.
*/
bool isSelected(const int x, const int y) const;
/**
* Convenience method. Returns the currently selected text.
* @param options See Screen::DecodingOptions
*/
QString selectedText(const DecodingOptions options) const;
/**
* Convenience method. Returns the text between two indices.
* @param startIndex Specifies the starting text index
* @param endIndex Specifies the ending text index
* @param options See Screen::DecodingOptions
*/
QString text(int startIndex, int endIndex, const DecodingOptions options) const;
/**
* Copies part of the output to a stream.
*
* @param decoder A decoder which converts terminal characters into text
* @param fromLine The first line in the history to retrieve
* @param toLine The last line in the history to retrieve
*/
void writeLinesToStream(TerminalCharacterDecoder *decoder, int fromLine, int toLine) const;
/**
* Checks if the text between from and to is inside the current
* selection. If this is the case, the selection is cleared. The
* from and to are coordinates in the current viewable window.
* The loc(x,y) macro can be used to generate these values from a
* column,line pair.
*
* @param from The start of the area to check.
* @param to The end of the area to check
*/
void checkSelection(int from, int to);
/**
* Sets or clears an attribute of the current line.
*
* @param property The attribute to set or clear
* Possible properties are:
* LINE_WRAPPED: Specifies that the line is wrapped.
* LINE_DOUBLEWIDTH: Specifies that the characters in the current line
* should be double the normal width.
* LINE_DOUBLEHEIGHT_TOP:
* LINE_DOUBLEHEIGHT_BOTTOM: Specifies that the characters in the current line
* should be double the normal height.
* Double-height lines are formed of two lines containing the same characters,
* with the top one having the LINE_DOUBLEHEIGHT_TOP attribute
* and the bottom one having the LINE_DOUBLEHEIGHT_BOTTOM attribute.
* This allows other parts of the code to work on the
* assumption that all lines are the same height.
*
* @param enable true to apply the attribute to the current line or false to remove it
*/
void setLineProperty(quint16 property, bool enable);
/**
* Set REPL mode (shell integration)
*
* @param mode is the REPL mode
* Possible modes are:
* REPL_None
* REPL_PROMPT
* REPL_INPUT
* REPL_OUTPUT
*/
void setReplMode(int mode);
void setExitCode(int exitCode);
/** Return true if semantic shell integration is in use. */
bool hasRepl() const
{
return _hasRepl;
}
/** Return current REPL mode */
int replMode() const
{
return _replMode;
}
/** Return location of current REPL mode start. */
std::pair<int, int> replModeStart() const
{
return _replModeStart;
}
std::pair<int, int> replModeEnd() const
{
return _replModeEnd;
}
/**
* Returns the number of lines that the image has been scrolled up or down by,
* since the last call to resetScrolledLines().
*
* a positive return value indicates that the image has been scrolled up,
* a negative return value indicates that the image has been scrolled down.
*/
int scrolledLines() const;
/**
* Returns the region of the image which was last scrolled.
*
* This is the area of the image from the top margin to the
* bottom margin when the last scroll occurred.
*/
QRect lastScrolledRegion() const;
/**
* Resets the count of the number of lines that the image has been scrolled up or down by,
* see scrolledLines()
*/
void resetScrolledLines();
/**
* Returns the number of lines of output which have been
* dropped from the history since the last call
* to resetDroppedLines()
*
* If the history is not unlimited then it will drop
* the oldest lines of output if new lines are added when
* it is full.
*/
int droppedLines() const;
/**
* Resets the count of the number of lines dropped from
* the history.
*/
void resetDroppedLines();
/**
* Fills the buffer @p dest with @p count instances of the default (ie. blank)
* Character style.
*/
static void fillWithDefaultChar(Character *dest, int count);
void setCurrentTerminalDisplay(TerminalDisplay *display)
{
_currentTerminalDisplay = display;
}
TerminalDisplay *currentTerminalDisplay()
{
return _currentTerminalDisplay;
}
QSet<uint> usedExtendedChars() const
{
QSet<uint> result;
for (int i = 0; i < _lines; ++i) {
const ImageLine &il = _screenLines[i];
for (int j = 0; j < il.length(); ++j) {
if (il[j].rendition.f.extended) {
result << il[j].character;
}
}
}
return result;
}
void setEnableUrlExtractor(const bool enable);
static const Character DefaultChar;
// Return the total number of lines before resize (fix scroll glitch)
int getOldTotalLines();
// Return if it was a resize signal (fix scroll glitch)
bool isResize();
// Set reflow condition
void setReflowLines(bool enable);
/* Graphics display functions */
void addPlacement(QPixmap pixmap,
int &rows,
int &cols,
int row = -1,
int col = -1,
bool scrolling = true,
int moveCursor = 1,
bool leaveText = false,
int z = -1000,
int id = -1,
int pid = -1,
qreal opacity = 1.0,
int X = 0,
int Y = 0);
TerminalGraphicsPlacement_t *getGraphicsPlacement(unsigned int i);
void delPlacements(int = 'a', qint64 = 0, qint64 = -1, int = 0, int = 0, int = 0);
bool hasGraphics() const
{
return _hasGraphics;
}
void setIgnoreWcWidth(bool ignore);
private:
// copies a line of text from the screen or history into a stream using a
// specified character decoder. Returns the number of lines actually copied,
// which may be less than 'count' if (start+count) is more than the number of characters on
// the line
//
// line - the line number to copy, from 0 (the earliest line in the history) up to
// history->getLines() + lines - 1
// start - the first column on the line to copy
// count - the number of characters on the line to copy
// decoder - a decoder which converts terminal characters (an Character array) into text
// appendNewLine - if true a new line character (\n) is appended to the end of the line
// isBlockSelectionMode - takes that in consideration while appending a new line.
int copyLineToStream(int line,
int start,
int count,
TerminalCharacterDecoder *decoder,
bool appendNewLine,
bool isBlockSelectionMode,
const DecodingOptions options) const;
// fills a section of the screen image with the character 'c'
// the parameters are specified as offsets from the start of the screen image.
// the loc(x,y) macro can be used to generate these values from a column,line pair.
// if resetLineRendition is true, all completely cleared lines will be set to single-width.
void clearImage(int loca, int loce, char c, bool resetLineRendition = true);
// erases a rectangular section of the screen.
void eraseBlock(int y, int x, int height, int width);
// move screen image between 'sourceBegin' and 'sourceEnd' to 'dest'.
// the parameters are specified as offsets from the start of the screen image.
// the loc(x,y) macro can be used to generate these values from a column,line pair.
//
// NOTE: moveImage() can only move whole lines
void moveImage(int dest, int sourceBegin, int sourceEnd);
// scroll up 'n' lines in current region, clearing the bottom 'n' lines
void scrollUp(int from, int n);
// scroll down 'n' lines in current region, clearing the top 'n' lines
void scrollDown(int from, int n);
// when we handle scroll commands, we need to know which screenwindow will scroll
TerminalDisplay *_currentTerminalDisplay;
void addHistLine();
// add lines from _screen to _history and remove from _screen the added lines (used to resize lines and columns)
void fastAddHistLine();
void initTabStops();
void updateEffectiveRendition();
void reverseRendition(Character &p) const;
bool isSelectionValid() const;
// copies text from 'startIndex' to 'endIndex' to a stream
// startIndex and endIndex are positions generated using the loc(x,y) macro
void writeToStream(TerminalCharacterDecoder *decoder, int startIndex, int endIndex, const DecodingOptions options) const;
// copies 'count' lines from the screen buffer into 'dest',
// starting from 'startLine', where 0 is the first line in the screen buffer
void copyFromScreen(Character *dest, int startLine, int count) const;
// copies 'count' lines from the history buffer into 'dest',
// starting from 'startLine', where 0 is the first line in the history
void copyFromHistory(Character *dest, int startLine, int count) const;
Character getCharacter(int col, int row) const;
// returns a buffer that can hold at most 'count' characters,
// where the number of reallocations and object reinitializations
// should be as minimal as possible
static Character *getCharacterBuffer(const int size);
// Returns if its app mode or not
inline bool isAppMode()
{
return _currentModes[MODE_AppScreen] == 1;
}
// Get the cursor line after checking if its app mode or not
int getCursorLine();
// Set the cursor line after checking if its app mode or not
void setCursorLine(int newLine);
int getLineLength(const int line) const;
// returns the width in columns of the specified screen line,
// taking DECDWL/DECDHL (double width/height modes) into account.
int getScreenLineColumns(const int line) const;
// screen image ----------------
int _lines;
int _columns;
typedef QVector<Character> ImageLine; // [0..columns]
std::vector<ImageLine> _screenLines; // [lines]
int _screenLinesSize; // _screenLines.size()
int _scrolledLines;
QRect _lastScrolledRegion;
int _droppedLines;
int _oldTotalLines;
bool _isResize;
bool _enableReflowLines;
std::vector<LineProperty> _lineProperties;
LineProperty linePropertiesAt(unsigned int line);
// history buffer ---------------
std::unique_ptr<HistoryScroll> _history;
// cursor location
int _cuX;
int _cuY;
// select mode cursor location
int _selCuX{};
int _selCuY{};
// cursor color and rendition info
CharacterColor _currentForeground;
CharacterColor _currentBackground;
RenditionFlagsC _currentRendition;
CharacterColor _ulColors[15];
int _ulColorQueueStart;
int _ulColorQueueEnd;
int _currentULColor;
// margins ----------------
int _topMargin;
int _bottomMargin;
// states ----------------
int _currentModes[MODES_SCREEN];
int _savedModes[MODES_SCREEN];
int _replMode;
bool _hasRepl;
bool _replHadOutput;
std::pair<int, int> _replModeStart;
std::pair<int, int> _replModeEnd;
std::pair<int, int> _replLastOutputStart;
std::pair<int, int> _replLastOutputEnd;
int commandCounter = 0;
// ----------------------------
QBitArray _tabStops;
// selection -------------------
int _selBegin; // The first location selected.
int _selTopLeft; // TopLeft Location.
int _selBottomRight; // Bottom Right Location.
bool _blockSelectionMode; // Column selection mode
// effective colors and rendition ------------
CharacterColor _effectiveForeground; // These are derived from
CharacterColor _effectiveBackground; // the cu_* variables above
RenditionFlagsC _effectiveRendition; // to speed up operation
class SavedState
{
public:
SavedState()
: cursorColumn(0)
, cursorLine(0)
, originMode(0)
, rendition({0})
, foreground(CharacterColor())
, background(CharacterColor())
{
}
int cursorColumn;
int cursorLine;
int originMode;
RenditionFlagsC rendition;
CharacterColor foreground;
CharacterColor background;
};
SavedState _savedState;
// last position where we added a character
int _lastPos;
// used in REP (repeating char)
quint32 _lastDrawnChar;
std::unique_ptr<EscapeSequenceUrlExtractor> _escapeSequenceUrlExtractor;
void toggleUrlInput();
// Vt102Emulation defined max argument value that can be passed to a Screen function
const int MAX_SCREEN_ARGUMENT = 40960;
/* Graphics */
void addPlacement(std::unique_ptr<TerminalGraphicsPlacement_t> &u);
std::vector<std::unique_ptr<TerminalGraphicsPlacement_t>> _graphicsPlacements;
void scrollPlacements(int n, qint64 below = INT64_MAX, qint64 above = INT64_MAX);
bool _hasGraphics;
//
bool _ignoreWcWidth;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Screen::DecodingOptions)
}
#endif // SCREEN_H
@@ -0,0 +1,355 @@
/*
SPDX-FileCopyrightText: 2007 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "ScreenWindow.h"
// Konsole
using namespace Konsole;
ScreenWindow::ScreenWindow(Screen *screen, QObject *parent)
: QObject(parent)
, _screen(nullptr)
, _windowBuffer(nullptr)
, _windowBufferSize(0)
, _bufferNeedsUpdate(true)
, _windowLines(1)
, _currentLine(0)
, _currentResultLine(-1)
, _trackOutput(true)
, _scrollCount(0)
{
setScreen(screen);
}
ScreenWindow::~ScreenWindow()
{
delete[] _windowBuffer;
}
void ScreenWindow::setScreen(Screen *screen)
{
Q_ASSERT(screen);
if (screen == _screen) {
return;
}
Q_EMIT screenAboutToChange();
_screen = screen;
}
Screen *ScreenWindow::screen() const
{
return _screen;
}
Character *ScreenWindow::getImage()
{
// reallocate internal buffer if the window size has changed
int size = windowLines() * windowColumns();
if (_windowBuffer == nullptr || _windowBufferSize != size) {
delete[] _windowBuffer;
_windowBufferSize = size;
_windowBuffer = new Character[size];
_bufferNeedsUpdate = true;
}
if (!_bufferNeedsUpdate) {
return _windowBuffer;
}
_screen->getImage(_windowBuffer, size, currentLine(), endWindowLine());
// this window may look beyond the end of the screen, in which
// case there will be an unused area which needs to be filled
// with blank characters
fillUnusedArea();
_bufferNeedsUpdate = false;
return _windowBuffer;
}
void ScreenWindow::fillUnusedArea()
{
int screenEndLine = _screen->getHistLines() + _screen->getLines() - 1;
int windowEndLine = currentLine() + windowLines() - 1;
int unusedLines = windowEndLine - screenEndLine;
// stop when unusedLines is negative; there is an issue w/ charsToFill
// being greater than an int can hold
if (unusedLines <= 0) {
return;
}
int charsToFill = unusedLines * windowColumns();
Screen::fillWithDefaultChar(_windowBuffer + _windowBufferSize - charsToFill, charsToFill);
}
// return the index of the line at the end of this window, or if this window
// goes beyond the end of the screen, the index of the line at the end
// of the screen.
//
// when passing a line number to a Screen method, the line number should
// never be more than endWindowLine()
//
int ScreenWindow::endWindowLine() const
{
return qMin(currentLine() + windowLines() - 1, lineCount() - 1);
}
QVector<LineProperty> ScreenWindow::getLineProperties()
{
QVector<LineProperty> result = _screen->getLineProperties(currentLine(), endWindowLine());
if (result.count() != windowLines()) {
result.resize(windowLines());
}
return result;
}
QString ScreenWindow::selectedText(const Screen::DecodingOptions options) const
{
return _screen->selectedText(options);
}
void ScreenWindow::getSelectionStart(int &column, int &line)
{
_screen->getSelectionStart(column, line);
line -= currentLine();
}
void ScreenWindow::getSelectionEnd(int &column, int &line)
{
_screen->getSelectionEnd(column, line);
line -= currentLine();
}
void ScreenWindow::setSelectionStart(int column, int line, bool columnMode)
{
_screen->setSelectionStart(column, line + currentLine(), columnMode);
_bufferNeedsUpdate = true;
Q_EMIT selectionChanged();
}
void ScreenWindow::setSelectionEnd(int column, int line, bool trimTrailingWhitespace)
{
_screen->setSelectionEnd(column, line + currentLine(), trimTrailingWhitespace);
_bufferNeedsUpdate = true;
Q_EMIT selectionChanged();
}
void ScreenWindow::setSelectionByLineRange(int start, int end)
{
clearSelection();
_screen->setSelectionStart(0, start, false);
_screen->setSelectionEnd(windowColumns(), end, false);
_bufferNeedsUpdate = true;
Q_EMIT selectionChanged();
}
bool ScreenWindow::isSelected(int column, int line)
{
return _screen->isSelected(column, qMin(line + currentLine(), endWindowLine()));
}
void ScreenWindow::clearSelection()
{
_screen->clearSelection();
Q_EMIT selectionChanged();
}
void ScreenWindow::setWindowLines(int lines)
{
Q_ASSERT(lines > 0);
_windowLines = lines;
}
int ScreenWindow::windowLines() const
{
return _windowLines;
}
int ScreenWindow::windowColumns() const
{
return _screen->getColumns();
}
int ScreenWindow::lineCount() const
{
return _screen->getHistLines() + _screen->getLines();
}
int ScreenWindow::columnCount() const
{
return _screen->getColumns();
}
QPoint ScreenWindow::cursorPosition() const
{
QPoint position;
position.setX(_screen->getCursorX());
position.setY(_screen->getCursorY());
return position;
}
int ScreenWindow::currentLine() const
{
return qBound(0, _currentLine, std::max(0, lineCount() - windowLines()));
}
int ScreenWindow::currentResultLine() const
{
return _currentResultLine;
}
void ScreenWindow::setCurrentResultLine(int line)
{
if (_currentResultLine == line) {
return;
}
_currentResultLine = line;
Q_EMIT currentResultLineChanged();
}
void ScreenWindow::scrollBy(RelativeScrollMode mode, int amount, bool fullPage)
{
if (mode == ScrollLines) {
scrollTo(currentLine() + amount);
} else if (mode == ScrollPages || (mode == ScrollPrompts && !_screen->hasRepl())) {
if (fullPage) {
scrollTo(currentLine() + amount * (windowLines()));
} else {
scrollTo(currentLine() + amount * (windowLines() / 2));
}
} else if (mode == ScrollPrompts) {
int i = currentLine();
if (amount < 0) {
QVector<LineProperty> properties = _screen->getLineProperties(0, currentLine());
while (i > 0 && amount < 0) {
i--;
if ((properties[i].flags.f.prompt_start) != 0) {
if (++amount == 0) {
break;
}
}
}
} else if (amount > 0) {
QVector<LineProperty> properties = _screen->getLineProperties(currentLine(), _screen->getHistLines());
while (i < _screen->getHistLines() && amount > 0) {
i++;
if ((properties[i - currentLine()].flags.f.prompt_start) != 0) {
if (--amount == 0) {
break;
}
}
}
}
scrollTo(i);
}
}
bool ScreenWindow::atEndOfOutput() const
{
return currentLine() == (lineCount() - windowLines());
}
void ScreenWindow::scrollTo(int line)
{
int maxCurrentLineNumber = lineCount() - windowLines();
line = qBound(0, line, maxCurrentLineNumber);
const int delta = line - _currentLine;
_currentLine = line;
// keep track of number of lines scrolled by,
// this can be reset by calling resetScrollCount()
_scrollCount += delta;
_bufferNeedsUpdate = true;
Q_EMIT scrolled(_currentLine);
}
void ScreenWindow::setTrackOutput(bool trackOutput)
{
_trackOutput = trackOutput;
}
bool ScreenWindow::trackOutput() const
{
return _trackOutput;
}
int ScreenWindow::scrollCount() const
{
return _scrollCount;
}
void ScreenWindow::resetScrollCount()
{
_scrollCount = 0;
}
QRect ScreenWindow::scrollRegion() const
{
bool equalToScreenSize = windowLines() == _screen->getLines();
if (atEndOfOutput() && equalToScreenSize) {
return _screen->lastScrolledRegion();
}
return {0, 0, windowColumns(), windowLines()};
}
void ScreenWindow::updateCurrentLine()
{
if (!_screen->isResize()) {
return;
}
if (_currentLine > 0) {
_currentLine -= _screen->getOldTotalLines() - lineCount();
}
_currentLine = currentLine();
}
void ScreenWindow::notifyOutputChanged()
{
// move window to the bottom of the screen and update scroll count
// if this window is currently tracking the bottom of the screen
if (_trackOutput) {
_scrollCount -= _screen->scrolledLines();
_currentLine = qMax(0, _screen->getHistLines() - (windowLines() - _screen->getLines()));
} else {
// if the history is not unlimited then it may
// have run out of space and dropped the oldest
// lines of output - in this case the screen
// window's current line number will need to
// be adjusted - otherwise the output will scroll
_currentLine = qMax(0, _currentLine - _screen->droppedLines());
// ensure that the screen window's current position does
// not go beyond the bottom of the screen
_currentLine = qMin(_currentLine, _screen->getHistLines());
}
_bufferNeedsUpdate = true;
Q_EMIT outputChanged();
}
#include "moc_ScreenWindow.cpp"
@@ -0,0 +1,278 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SCREENWINDOW_H
#define SCREENWINDOW_H
// Qt
#include <QObject>
#include <QPoint>
#include <QRect>
// Konsole
#include "../characters/Character.h"
#include "Screen.h"
#include "konsoleprivate_export.h"
namespace Konsole
{
/**
* Provides a window onto a section of a terminal screen. A terminal widget can then render
* the contents of the window and use the window to change the terminal screen's selection
* in response to mouse or keyboard input.
*
* A new ScreenWindow for a terminal session can be created by calling Emulation::createWindow()
*
* Use the scrollTo() method to scroll the window up and down on the screen.
* Use the getImage() method to retrieve the character image which is currently visible in the window.
*
* setTrackOutput() controls whether the window moves to the bottom of the associated screen when new
* lines are added to it.
*
* Whenever the output from the underlying screen is changed, the notifyOutputChanged() slot should
* be called. This in turn will update the window's position and emit the outputChanged() signal
* if necessary.
*/
class KONSOLEPRIVATE_EXPORT ScreenWindow : public QObject
{
Q_OBJECT
public:
/**
* Constructs a new screen window with the given parent.
* A screen must be specified by calling setScreen() before calling getImage() or getLineProperties().
*
* You should not call this constructor directly, instead use the Emulation::createWindow() method
* to create a window on the emulation which you wish to view. This allows the emulation
* to notify the window when the associated screen has changed and synchronize selection updates
* between all views on a session.
*/
explicit ScreenWindow(Screen *screen, QObject *parent = nullptr);
~ScreenWindow() override;
/** Sets the screen which this window looks onto */
void setScreen(Screen *screen);
/** Returns the screen which this window looks onto */
Screen *screen() const;
/**
* Returns the image of characters which are currently visible through this window
* onto the screen.
*
* The returned buffer is managed by the ScreenWindow instance and does not need to be
* deleted by the caller.
*/
Character *getImage();
/**
* Returns the line attributes associated with the lines of characters which
* are currently visible through this window
*/
QVector<LineProperty> getLineProperties();
/**
* Returns the number of lines which the region of the window
* specified by scrollRegion() has been scrolled by since the last call
* to resetScrollCount(). scrollRegion() is in most cases the
* whole window, but will be a smaller area in, for example, applications
* which provide split-screen facilities.
*
* This is not guaranteed to be accurate, but allows views to optimize
* rendering by reducing the amount of costly text rendering that
* needs to be done when the output is scrolled.
*/
int scrollCount() const;
/**
* Resets the count of scrolled lines returned by scrollCount()
*/
void resetScrollCount();
/**
* Returns the area of the window which was last scrolled, this is
* usually the whole window area.
*
* Like scrollCount(), this is not guaranteed to be accurate,
* but allows views to optimize rendering.
*/
QRect scrollRegion() const;
/**
* What line the next search will start from
*/
void setCurrentResultLine(int line);
int currentResultLine() const;
/**
* Sets the start of the selection to the given @p line and @p column within
* the window.
*/
void setSelectionStart(int column, int line, bool columnMode);
/**
* Sets the end of the selection to the given @p line and @p column within
* the window.
*/
void setSelectionEnd(int column, int line, bool trimTrailingWhitespace);
/**
* Sets the selection as the range specified by line @p start and line @p
* end in the whole history.
*
* Both @p start and @p end are absolute line number in the whole history,
* not relative line number in the window. This make it possible to select
* range larger than the window . A good use case is selecting the whole
* history.
*/
void setSelectionByLineRange(int start, int end);
/**
* Retrieves the start of the selection within the window.
*/
void getSelectionStart(int &column, int &line);
/**
* Retrieves the end of the selection within the window.
*/
void getSelectionEnd(int &column, int &line);
/**
* Returns true if the character at @p line , @p column is part of the selection.
*/
bool isSelected(int column, int line);
/**
* Clears the current selection
*/
void clearSelection();
/** Sets the number of lines in the window */
void setWindowLines(int lines);
/** Returns the number of lines in the window */
int windowLines() const;
/** Returns the number of columns in the window */
int windowColumns() const;
/** Returns the total number of lines in the screen */
int lineCount() const;
/** Returns the total number of columns in the screen */
int columnCount() const;
/** Returns the index of the line which is currently at the top of this window */
int currentLine() const;
/**
* Returns the position of the cursor
* within the window.
*/
QPoint cursorPosition() const;
/**
* Convenience method. Returns true if the window is currently at the bottom
* of the screen.
*/
bool atEndOfOutput() const;
/** Scrolls the window so that @p line is at the top of the window */
void scrollTo(int line);
/** Describes the units which scrollBy() moves the window by. */
enum RelativeScrollMode {
/** Scroll the window down by a given number of lines. */
ScrollLines,
/**
* Scroll the window down by a given number of pages, where
* one page is windowLines() lines
*/
ScrollPages,
/** Scroll by prompt (when using semantic shell integration)
* scrolls up/down until the given number of new prompts appear.
*/
ScrollPrompts,
};
/**
* Scrolls the window relative to its current position on the screen.
*
* @param mode Specifies whether @p amount refers to the number of lines or the number
* of pages to scroll.
* @param amount The number of lines or pages ( depending on @p mode ) to scroll by. If
* this number is positive, the view is scrolled down. If this number is negative, the view
* is scrolled up.
* @param fullPage Specifies whether to scroll by full page or half page.
*/
void scrollBy(RelativeScrollMode mode, int amount, bool fullPage);
/**
* Specifies whether the window should automatically move to the bottom
* of the screen when new output is added.
*
* If this is set to true, the window will be moved to the bottom of the associated screen ( see
* screen() ) when the notifyOutputChanged() method is called.
*/
void setTrackOutput(bool trackOutput);
/**
* Returns whether the window automatically moves to the bottom of the screen as
* new output is added. See setTrackOutput()
*/
bool trackOutput() const;
/**
* Returns the text which is currently selected.
*
* @param options See Screen::DecodingOptions
*/
QString selectedText(const Screen::DecodingOptions options) const;
void updateCurrentLine();
public Q_SLOTS:
/**
* Notifies the window that the contents of the associated terminal screen have changed.
* This moves the window to the bottom of the screen if trackOutput() is true and causes
* the outputChanged() signal to be emitted.
*/
void notifyOutputChanged();
Q_SIGNALS:
/**
* Emitted when the contents of the associated terminal screen (see screen()) changes.
*/
void outputChanged();
void currentResultLineChanged();
/**
* Emitted when the screen window is scrolled to a different position.
*
* @param line The line which is now at the top of the window.
*/
void scrolled(int line);
/** Emitted when the selection is changed. */
void selectionChanged();
/** Emitted when e. g. Screen is changed (to alternate or primary), so
* initial selection offsets etc. are invalidated.
*/
void screenAboutToChange();
private:
Q_DISABLE_COPY(ScreenWindow)
int endWindowLine() const;
void fillUnusedArea();
Screen *_screen; // see setScreen() , screen()
Character *_windowBuffer;
int _windowBufferSize;
bool _bufferNeedsUpdate;
int _windowLines;
int _currentLine; // see scrollTo() , currentLine()
int _currentResultLine;
bool _trackOutput; // see setTrackOutput() , trackOutput()
int _scrollCount; // count of lines which the window has been scrolled by since
// the last call to resetScrollCount()
};
}
#endif // SCREENWINDOW_H
@@ -0,0 +1,54 @@
/*
SPDX-FileCopyrightText: 2015 Lindsay Roberts <os@lindsayr.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "ScrollState.h"
#include <QWheelEvent>
using namespace Konsole;
void ScrollState::addWheelEvent(const QWheelEvent *wheel)
{
if ((wheel->angleDelta().y() != 0) && (wheel->pixelDelta().y() == 0)) {
_remainingScrollPixel = 0;
} else {
_remainingScrollPixel += wheel->pixelDelta().y();
}
_remainingScrollAngle += wheel->angleDelta().y();
}
void ScrollState::clearAll()
{
_remainingScrollPixel = 0;
_remainingScrollAngle = 0;
}
int ScrollState::consumeLegacySteps(int stepsize)
{
if (stepsize < 1) {
stepsize = DEFAULT_ANGLE_SCROLL_LINE;
}
const int steps = _remainingScrollAngle / stepsize;
_remainingScrollAngle -= steps * stepsize;
if (steps != 0) {
_remainingScrollPixel = 0;
}
return steps;
}
int ScrollState::consumeSteps(int pixelStepSize, int angleStepSize)
{
if (_remainingScrollPixel != 0) {
int steps = _remainingScrollPixel / pixelStepSize;
_remainingScrollPixel -= steps * pixelStepSize;
_remainingScrollAngle = 0;
return steps;
}
int steps = _remainingScrollAngle / angleStepSize;
_remainingScrollAngle -= steps * angleStepSize;
_remainingScrollPixel = 0;
return steps;
}
@@ -0,0 +1,73 @@
/*
SPDX-FileCopyrightText: 2015 Lindsay Roberts <os@lindsayr.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SCROLLSTATE_H
#define SCROLLSTATE_H
class QWheelEvent;
namespace Konsole
{
/**
* Represents accumulation of wheel scroll from scroll events.
*
* Modern high precision scroll events supply many smaller events
* that may or may not translate into a UI action to support smooth
* pixel level scrolling. Builtin classes such as QScrollBar
* support these events under Qt5, but custom code
* written to handle scroll events in other ways must be modified
* to accumulate small deltas and act when suitable thresholds have
* been reached (ideally 1 for pixel scroll values towards any action
* that can be mapped to a pixel movement).
*/
struct ScrollState {
enum {
DEFAULT_ANGLE_SCROLL_LINE = 120,
};
enum {
DEGREES_PER_ANGLE_UNIT = 8,
};
static inline int degreesToAngle(const int angle)
{
return angle * DEGREES_PER_ANGLE_UNIT;
}
int angle() const
{
return _remainingScrollAngle;
}
int pixel() const
{
return _remainingScrollPixel;
}
/** Add scroll values from a QWheelEvent to the accumulated totals */
void addWheelEvent(const QWheelEvent *wheel);
/** Clear all - used when scroll is consumed by another class like QScrollBar */
void clearAll();
/** Return the (signed) multiple of @p stepsize available and subtract it from
* accumulated totals. Also clears accumulated pixel scroll. */
int consumeLegacySteps(int stepsize);
/** Return the (signed) multiple of @p pixelStepSize if any pixel scroll is
* available - that is, if pixel scroll is being supplied, or the same from
* the @p angleStepSize else. The corresponding value is subtracted from
* the accumulated total. The other scroll style value is cleared. */
int consumeSteps(int pixelStepSize, int angleStepSize);
ScrollState()
: _remainingScrollAngle(0)
, _remainingScrollPixel(0)
{
}
int _remainingScrollAngle;
int _remainingScrollPixel;
};
}
#endif // SCROLLSTATE_H
@@ -0,0 +1,239 @@
/*
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 2009 Thomas Dreibholz <dreibh@iem.uni-due.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "SearchHistoryTask.h"
#include <QApplication>
#include <QTextStream>
#include "../decoders/PlainTextDecoder.h"
#include "Emulation.h"
namespace Konsole
{
void SearchHistoryTask::addScreenWindow(Session *session, ScreenWindow *searchWindow)
{
_windows.insert(session, searchWindow);
}
void SearchHistoryTask::execute()
{
auto iter = QMapIterator<QPointer<Session>, ScreenWindowPtr>(_windows);
while (iter.hasNext()) {
iter.next();
executeOnScreenWindow(iter.key(), iter.value());
}
if (autoDelete()) {
deleteLater();
}
}
void SearchHistoryTask::executeOnScreenWindow(const QPointer<Session> &session, const ScreenWindowPtr &window)
{
Q_ASSERT(session);
Q_ASSERT(window);
Emulation *emulation = session->emulation();
if (!_regExp.pattern().isEmpty()) {
int pos = -1;
bool forwards = (_direction == Enum::ForwardsSearch);
const int lastLine = window->lineCount() - 1;
int startLine = _startLine;
if (forwards && (_startLine == lastLine)) {
if (!_noWrap) {
startLine = 0;
}
} else if (!forwards && (_startLine == 0)) {
if (!_noWrap) {
startLine = lastLine;
}
} else {
startLine = _startLine + (forwards ? 1 : -1);
}
QString string;
// text stream to read history into string for pattern or regular expression searching
QTextStream searchStream(&string);
PlainTextDecoder decoder;
decoder.setRecordLinePositions(true);
// setup first and last lines depending on search direction
int line = startLine;
// read through and search history in blocks of 10K lines.
// this balances the need to retrieve lots of data from the history each time
//(for efficient searching)
// without using silly amounts of memory if the history is very large.
const int maxDelta = qMin(window->lineCount(), 10000);
int delta = forwards ? maxDelta : -maxDelta;
int endLine = line;
bool hasWrapped = false; // set to true when we reach the top/bottom
// of the output and continue from the other
// end
// loop through history in blocks of <delta> lines.
do {
// ensure that application does not appear to hang
// if searching through a lengthy output
QApplication::processEvents();
// calculate lines to search in this iteration
if (hasWrapped) {
if (endLine == lastLine) {
line = 0;
} else if (endLine == 0) {
line = lastLine;
}
endLine += delta;
if (forwards) {
endLine = qMin(startLine, endLine);
} else {
endLine = qMax(startLine, endLine);
}
} else {
endLine += delta;
if (endLine > lastLine) {
hasWrapped = true;
endLine = lastLine;
} else if (endLine < 0) {
hasWrapped = true;
endLine = 0;
}
}
decoder.begin(&searchStream);
emulation->writeToStream(&decoder, qMin(endLine, line), qMax(endLine, line));
decoder.end();
// line number search below assumes that the buffer ends with a new-line
string.append(QLatin1Char('\n'));
if (forwards) {
pos = string.indexOf(_regExp);
} else {
pos = string.lastIndexOf(_regExp);
}
// if a match is found, position the cursor on that line and update the screen
if (pos != -1) {
int newLines = 0;
QList<int> linePositions = decoder.linePositions();
while (newLines < linePositions.count() && linePositions[newLines] <= pos) {
newLines++;
}
// ignore the new line at the start of the buffer
newLines--;
int findPos = qMin(line, endLine) + newLines;
highlightResult(window, findPos);
Q_EMIT completed(true);
return;
}
// if noWrap is checked and we wrapped, set cursor at last match
if (hasWrapped && _noWrap) {
// invert search direction
forwards = !forwards;
delta = -delta;
endLine += (forwards ? 1 : -1);
hasWrapped = false;
}
// clear the current block of text and move to the next one
string.clear();
line = endLine;
} while (startLine != endLine);
if (!session->getSelectMode()) {
// if no match was found, clear selection to indicate this,
window->clearSelection();
window->notifyOutputChanged();
}
}
Q_EMIT completed(false);
}
void SearchHistoryTask::highlightResult(const ScreenWindowPtr &window, int findPos)
{
// work out how many lines into the current block of text the search result was found
//- looks a little painful, but it only has to be done once per search.
////qDebug() << "Found result at line " << findPos;
// update display to show area of history containing selection
if ((findPos < window->currentLine()) || (findPos >= (window->currentLine() + window->windowLines()))) {
int centeredScrollPos = findPos - window->windowLines() / 2;
if (centeredScrollPos < 0) {
centeredScrollPos = 0;
}
window->scrollTo(centeredScrollPos);
}
window->setTrackOutput(false);
window->notifyOutputChanged();
window->setCurrentResultLine(findPos);
}
SearchHistoryTask::SearchHistoryTask(QObject *parent)
: SessionTask(parent)
, _direction(Enum::BackwardsSearch)
, _noWrap(false)
, _startLine(0)
{
}
void SearchHistoryTask::setSearchDirection(Enum::SearchDirection direction)
{
_direction = direction;
}
void SearchHistoryTask::setStartLine(int line)
{
_startLine = line;
}
Enum::SearchDirection SearchHistoryTask::searchDirection() const
{
return _direction;
}
void SearchHistoryTask::setRegExp(const QRegularExpression &expression)
{
_regExp = expression;
}
QRegularExpression SearchHistoryTask::regExp() const
{
return _regExp;
}
void SearchHistoryTask::setNoWrap(bool noWrap)
{
_noWrap = noWrap;
}
bool SearchHistoryTask::noWrap() const
{
return _noWrap;
}
}
#include "moc_SearchHistoryTask.cpp"
@@ -0,0 +1,95 @@
/*
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 2009 Thomas Dreibholz <dreibh@iem.uni-due.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SEARCHHISTORYTASK_H
#define SEARCHHISTORYTASK_H
#include <QMap>
#include <QPointer>
#include <QRegularExpression>
#include "Enumeration.h"
#include "ScreenWindow.h"
#include "konsoleprivate_export.h"
#include "session/Session.h"
#include "session/SessionTask.h"
namespace Konsole
{
// class SearchHistoryThread;
/**
* A task which searches through the output of sessions for matches for a given regular expression.
* SearchHistoryTask operates on ScreenWindow instances rather than sessions added by addSession().
* A screen window can be added to the list to search using addScreenWindow()
*
* When execute() is called, the search begins in the direction specified by searchDirection(),
* starting at the position of the current selection.
*
* FIXME - This is not a proper implementation of SessionTask, in that it ignores sessions specified
* with addSession()
*
* TODO - Implementation requirements:
* May provide progress feedback to the user when searching very large output logs.
*/
class KONSOLEPRIVATE_EXPORT SearchHistoryTask : public SessionTask
{
Q_OBJECT
public:
/**
* Constructs a new search task.
*/
explicit SearchHistoryTask(QObject *parent = nullptr);
/** Adds a screen window to the list to search when execute() is called. */
void addScreenWindow(Session *session, ScreenWindow *searchWindow);
/** Sets the regular expression which is searched for when execute() is called */
void setRegExp(const QRegularExpression &expression);
/** Returns the regular expression which is searched for when execute() is called */
QRegularExpression regExp() const;
/** Specifies the direction to search in when execute() is called. */
void setSearchDirection(Enum::SearchDirection direction);
/** Returns the current search direction. See setSearchDirection(). */
Enum::SearchDirection searchDirection() const;
/** Specifies if search should wrap when execute() is called. */
void setNoWrap(bool noWrap);
/** Returns the current noWrap value. See setNoWrap(). */
bool noWrap() const;
/** The line from which the search will be done **/
void setStartLine(int line);
/**
* Performs a search through the session's history, starting at the position
* of the current selection, in the direction specified by setSearchDirection().
*
* If it finds a match, the ScreenWindow specified in the constructor is
* scrolled to the position where the match occurred and the selection
* is set to the matching text. execute() then returns immediately.
*
* To continue the search looking for further matches, call execute() again.
*/
void execute() override;
private:
using ScreenWindowPtr = QPointer<ScreenWindow>;
void executeOnScreenWindow(const QPointer<Session> &session, const ScreenWindowPtr &window);
void highlightResult(const ScreenWindowPtr &window, int findPos);
QMap<QPointer<Session>, ScreenWindowPtr> _windows;
QRegularExpression _regExp;
Enum::SearchDirection _direction;
bool _noWrap;
int _startLine;
};
}
#endif
@@ -0,0 +1,143 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "ShellCommand.h"
// KDE
#include <KShell>
using Konsole::ShellCommand;
ShellCommand::ShellCommand(const QString &aCommand)
: _arguments(KShell::splitArgs(aCommand))
{
}
ShellCommand::ShellCommand(const QString &aCommand, const QStringList &aArguments)
: _arguments(aArguments)
{
if (!_arguments.isEmpty()) {
_arguments[0] = aCommand;
}
}
QString ShellCommand::fullCommand() const
{
QStringList quotedArgs(_arguments);
for (int i = 0; i < quotedArgs.count(); i++) {
QString arg = quotedArgs.at(i);
bool hasSpace = false;
for (int j = 0; j < arg.length(); j++) {
if (arg[j].isSpace()) {
hasSpace = true;
}
}
if (hasSpace) {
quotedArgs[i] = QLatin1Char('\"') + arg + QLatin1Char('\"');
}
}
return quotedArgs.join(QLatin1Char(' '));
}
QString ShellCommand::command() const
{
if (!_arguments.isEmpty()) {
return _arguments[0];
}
return QString();
}
QStringList ShellCommand::arguments() const
{
return _arguments;
}
QStringList ShellCommand::expand(const QStringList &items)
{
QStringList result;
result.reserve(items.size());
for (const QString &item : items) {
result << expand(item);
}
return result;
}
QString ShellCommand::expand(const QString &text)
{
QString result = text;
expandEnv(result);
return result;
}
bool ShellCommand::isValidEnvCharacter(const QChar &ch)
{
const ushort code = ch.unicode();
return isValidLeadingEnvCharacter(ch) || ('0' <= code && code <= '9');
}
bool ShellCommand::isValidLeadingEnvCharacter(const QChar &ch)
{
const ushort code = ch.unicode();
return (code == '_') || ('A' <= code && code <= 'Z');
}
/*
* expandEnv
*
* Expand environment variables in text. Escaped '$' characters are ignored.
* Return true if any variables were expanded
*/
bool ShellCommand::expandEnv(QString &text)
{
const QLatin1Char dollarChar('$');
const QLatin1Char backslashChar('\\');
int dollarPos = 0;
bool expanded = false;
// find and expand all environment variables beginning with '$'
while ((dollarPos = text.indexOf(dollarChar, dollarPos)) != -1) {
// if '$' is the last character, there is no way of expanding
if (dollarPos == text.length() - 1) {
break;
}
// skip escaped '$'
if (dollarPos > 0 && text.at(dollarPos - 1) == backslashChar) {
dollarPos++;
continue;
}
// if '$' is followed by an invalid leading character, skip this '$'
if (!isValidLeadingEnvCharacter(text.at(dollarPos + 1))) {
dollarPos++;
continue;
}
int endPos = dollarPos + 1;
Q_ASSERT(endPos < text.length());
while (endPos < text.length() && isValidEnvCharacter(text.at(endPos))) {
endPos++;
}
const int len = endPos - dollarPos;
const QString key = text.mid(dollarPos + 1, len - 1);
const QString value = QString::fromLocal8Bit(qgetenv(key.toLocal8Bit().constData()));
if (!value.isEmpty()) {
text.replace(dollarPos, len, value);
expanded = true;
dollarPos = dollarPos + value.length();
} else {
dollarPos = endPos;
}
}
return expanded;
}
@@ -0,0 +1,77 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SHELLCOMMAND_H
#define SHELLCOMMAND_H
// Qt
#include <QStringList>
namespace Konsole
{
/**
* A class to parse and extract information about shell commands.
*
* ShellCommand can be used to:
*
* <ul>
* <li>Take a command-line (eg "/bin/sh -c /path/to/my/script") and split it
* into its component parts (eg. the command "/bin/sh" and the arguments
* "-c","/path/to/my/script")
* </li>
* <li>Take a command and a list of arguments and combine them to
* form a complete command line.
* </li>
* <li>Determine whether the binary specified by a command exists in the
* user's PATH.
* </li>
* <li>Determine whether a command-line specifies the execution of
* another command as the root user using su/sudo etc.
* </li>
* </ul>
*/
class ShellCommand
{
public:
/**
* Constructs a ShellCommand from a command line.
*
* @param aCommand The command line to parse.
*/
explicit ShellCommand(const QString &aCommand);
/**
* Constructs a ShellCommand with the specified @p aCommand and @p aArguments.
*/
ShellCommand(const QString &aCommand, const QStringList &aArguments);
/** Returns the command. */
QString command() const;
/** Returns the arguments. */
QStringList arguments() const;
/**
* Returns the full command line.
*/
QString fullCommand() const;
/** Expands environment variables in @p text .*/
static QString expand(const QString &text);
/** Expands environment variables in each string in @p list. */
static QStringList expand(const QStringList &items);
static bool isValidEnvCharacter(const QChar &ch);
static bool isValidLeadingEnvCharacter(const QChar &ch);
private:
static bool expandEnv(QString &text);
QStringList _arguments;
};
}
#endif // SHELLCOMMAND_H
@@ -0,0 +1,28 @@
/*
SPDX-FileCopyrightText: 2015 René J.V. Bertin <rjvbertin@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SHORTCUT_H
#define SHORTCUT_H
#include <qnamespace.h>
namespace Konsole
{
/**
* Platform-specific main shortcut "opcode":
*/
enum Modifier {
#ifdef Q_OS_MACOS
// Use plain Command key for shortcuts
ACCEL = Qt::CTRL,
#else
// Use Ctrl+Shift for shortcuts
ACCEL = Qt::CTRL | Qt::SHIFT,
#endif
};
}
#endif // SHORTCUT_H
@@ -0,0 +1,22 @@
/*
This source file is part of Konsole, a terminal emulator.
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "ShouldApplyProperty.h"
using namespace Konsole;
ShouldApplyProperty::ShouldApplyProperty(const Profile::Ptr &profile, bool modifiedOnly)
: _profile(profile)
, _modifiedPropertiesOnly(modifiedOnly)
{
}
bool ShouldApplyProperty::shouldApply(Profile::Property property) const
{
return !_modifiedPropertiesOnly || _profile->isPropertySet(property);
}
@@ -0,0 +1,31 @@
/*
This source file is part of Konsole, a terminal emulator.
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SHOULDAPPLYPROPERTY_H
#define SHOULDAPPLYPROPERTY_H
#include "konsoleprivate_export.h"
#include "profile/Profile.h"
namespace Konsole
{
/** Utility class to simplify code in SessionManager::applyProfile(). */
class KONSOLEPRIVATE_EXPORT ShouldApplyProperty
{
public:
ShouldApplyProperty(const Profile::Ptr &profile, bool modifiedOnly);
bool shouldApply(Profile::Property property) const;
private:
const Profile::Ptr _profile;
bool _modifiedPropertiesOnly;
};
}
#endif
@@ -0,0 +1,141 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "UnixProcessInfo.h"
#ifndef Q_OS_WIN
// Unix
#include <arpa/inet.h>
#include <cerrno>
#include <netinet/in.h>
#include <pwd.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <unistd.h>
// Qt
#include <QDebug>
#include <QtGlobal>
#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPEN_BSD) || defined(Q_OS_MACOS)
#include <QSharedPointer>
#include <sys/sysctl.h>
#endif
using namespace Konsole;
UnixProcessInfo::UnixProcessInfo(int pid)
: ProcessInfo(pid)
{
setUserNameRequired(true);
}
void UnixProcessInfo::readProcessInfo(int pid)
{
// prevent _arguments from growing longer and longer each time this
// method is called.
clearArguments();
if (readProcInfo(pid)) {
readArguments(pid);
readCurrentDir(pid);
bool ok = false;
const QString &processNameString = name(&ok);
if (ok && processNameString == QLatin1String("sudo")) {
// Append process name along with sudo
const QVector<QString> &args = arguments(&ok);
if (ok && args.size() > 1) {
setName(processNameString + QStringLiteral(" ") + args[1]);
}
}
}
}
void UnixProcessInfo::readUserName()
{
bool ok = false;
const int uid = userId(&ok);
if (!ok) {
return;
}
struct passwd passwdStruct;
struct passwd *getpwResult;
char *getpwBuffer;
long getpwBufferSize;
int getpwStatus;
getpwBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX);
if (getpwBufferSize == -1) {
getpwBufferSize = 16384;
}
getpwBuffer = new char[getpwBufferSize];
if (getpwBuffer == nullptr) {
return;
}
getpwStatus = getpwuid_r(uid, &passwdStruct, getpwBuffer, getpwBufferSize, &getpwResult);
if ((getpwStatus == 0) && (getpwResult != nullptr)) {
setUserName(QLatin1String(passwdStruct.pw_name));
} else {
setUserName(QString());
qWarning() << "getpwuid_r returned error : " << getpwStatus;
}
delete[] getpwBuffer;
}
bool UnixProcessInfo::readArguments(int pid)
{
// used for LinuxProcessInfo and SolarisProcessInfo
// read command-line arguments file found at /proc/<pid>/cmdline
// the expected format is a list of strings delimited by null characters,
// and ending in a double null character pair.
QFile argumentsFile(QStringLiteral("/proc/%1/cmdline").arg(pid));
if (argumentsFile.open(QIODevice::ReadOnly)) {
QTextStream stream(&argumentsFile);
const QString &data = stream.readAll();
const QStringList &argList = data.split(QLatin1Char('\0'));
for (const QString &entry : argList) {
if (!entry.isEmpty()) {
addArgument(entry);
}
}
} else {
setFileError(argumentsFile.error());
}
return true;
}
#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPEN_BSD) || defined(Q_OS_MACOS)
QSharedPointer<struct kinfo_proc> UnixProcessInfo::getProcInfoStruct(int *managementInfoBase, int mibCount)
{
size_t structLength;
if (::sysctl(managementInfoBase, mibCount, NULL, &structLength, NULL, 0) == -1) {
qWarning() << "first sysctl() call failed with code " << errno;
return nullptr;
}
QSharedPointer<struct kinfo_proc> kInfoProc(new struct kinfo_proc[structLength]);
if (::sysctl(managementInfoBase, mibCount, kInfoProc.get(), &structLength, NULL, 0) == -1) {
qWarning() << "second sysctl() call failed with code " << errno;
return nullptr;
}
return kInfoProc;
}
#endif
#endif // Q_OS_WIN
@@ -0,0 +1,68 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef UNIXPROCESSINFO_H
#define UNIXPROCESSINFO_H
#include "ProcessInfo.h"
#if defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
#include <QSharedPointer>
#include <sys/sysctl.h>
#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPEN_BSD)
#include <sys/user.h>
#endif
#endif
namespace Konsole
{
#ifndef Q_OS_WIN
/**
* Implementation of ProcessInfo for Unix platforms which uses
* the /proc filesystem
*/
class UnixProcessInfo : public ProcessInfo
{
public:
/**
* Constructs a new instance of UnixProcessInfo.
* See ProcessInfo::newInstance()
*/
explicit UnixProcessInfo(int pid);
protected:
/**
* Implementation of ProcessInfo::readProcessInfo(); calls the
* four private methods below in turn.
*/
void readProcessInfo(int pid) override;
void readUserName(void) override;
bool readArguments(int pid) override;
#if defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
/**
* Allocates an array of struct kinfo_proc and calls sysctl internally to fill it up
* @param managementInfoBase management information base to pass to sysctl
* @param mibCount number of elements in managementInfoBase, also passed as argument to sysctl
* @return smart pointer to struct kinfo_proc[] and returns nullptr on failure
*/
QSharedPointer<struct kinfo_proc> getProcInfoStruct(int *managementInfoBase, int mibCount);
#endif
private:
/**
* Read the standard process information -- PID, parent PID, foreground PID.
* @param pid process ID to use
* @return true on success
*/
virtual bool readProcInfo(int pid) = 0;
};
#endif
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,534 @@
/*
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef VIEWMANAGER_H
#define VIEWMANAGER_H
// Qt
#include <QAction>
#include <QHash>
#include <QObject>
#include <QPointer>
#include "konsoleprivate_export.h"
// Konsole
class KActionCollection;
class KConfigGroup;
namespace Konsole
{
class ColorScheme;
class Profile;
class Session;
class SessionController;
class TabbedViewContainer;
class TabbedViewContainer;
class TerminalDisplay;
class ViewProperties;
class ViewSplitter;
/**
* Manages the terminal display widgets in a Konsole window or part.
*
* When a view manager is created, it constructs a tab widget ( accessed via
* widget() ) to hold one or more view splitters. Each view splitter holds
* one or more terminal displays and splitters.
*
* The view manager provides menu actions ( defined in the 'konsoleui.rc' XML file )
* to manipulate the views and view containers - for example, actions to split the view
* left/right or top/bottom, detach a view from the current window and navigate between
* views and containers. These actions are added to the collection specified in the
* ViewManager's constructor.
*
* The view manager provides facilities to construct display widgets for a terminal
* session and also to construct the SessionController which provides the menus and other
* user interface elements specific to each display/session pair.
*
*/
class KONSOLEPRIVATE_EXPORT ViewManager : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.konsole.Window")
public:
/**
* Constructs a new view manager with the specified @p parent.
* View-related actions defined in 'konsoleui.rc' are created
* and added to the specified @p collection.
*/
ViewManager(QObject *parent, KActionCollection *collection);
~ViewManager() override;
/**
* Creates a new view to display the output from and deliver input to @p session.
* Constructs a new container to hold the views if no container has yet been created.
*/
void createView(TabbedViewContainer *tabWidget, Session *session);
/*
* Applies the view-specific settings associated with specified @p profile
* to the terminal display @p view.
*/
void applyProfileToView(TerminalDisplay *view, const QExplicitlySharedDataPointer<Profile> &profile);
void toggleActionsBasedOnState();
/**
* Return the main widget for the view manager which
* holds all of the views managed by this ViewManager instance.
*/
QWidget *widget() const;
/**
* Returns the view manager's active view.
*/
QWidget *activeView() const;
/**
* Returns the list of view properties for views in the active container.
* Each view widget is associated with a ViewProperties instance which
* provides access to basic information about the session being
* displayed in the view, such as title, current directory and
* associated icon.
*/
QList<ViewProperties *> viewProperties() const;
/**
* This enum describes the available types of navigation widget
* which newly created containers can provide to allow navigation
* between open sessions.
*/
enum NavigationMethod {
/**
* Each container has a row of tabs (one per session) which the user
* can click on to navigate between open sessions.
*/
TabbedNavigation,
/** The container has no navigation widget. */
NoNavigation,
};
/**
* Describes the options for showing or hiding the container's navigation widget.
*/
enum NavigationVisibility {
NavigationNotSet, // Don't rely on this information, Only use the settings.
AlwaysShowNavigation,
ShowNavigationAsNeeded,
AlwaysHideNavigation,
};
/**
* Sets the visibility of the view container's navigation widget.
* The ViewContainer subclass is responsible for ensuring that this
* setting is respected as views are dded or removed from the container
*/
void setNavigationVisibility(NavigationVisibility navigationVisibility);
/** Returns the current mode for controlling the visibility of the
* view container's navigation widget.
*/
NavigationVisibility navigationVisibility() const;
/**
* Sets the type of widget provided to navigate between open sessions
* in a container. The changes will only apply to newly created containers.
*
* The default method is TabbedNavigation. To disable navigation widgets, call
* setNavigationMethod(ViewManager::NoNavigation) before creating any sessions.
*/
void setNavigationMethod(NavigationMethod method);
/**
* Returns the type of navigation widget created in new containers.
* See setNavigationMethod()
*/
NavigationMethod navigationMethod() const;
/**
* Returns the controller for the active view. activeViewChanged() is
* emitted when this changes.
*/
SessionController *activeViewController() const;
/**
* Session management
*/
void saveSessions(KConfigGroup &group);
void restoreSessions(const KConfigGroup &group);
int managerId() const;
/** Returns a list of sessions in this ViewManager */
QList<Session *> sessions()
{
return _sessionMap.values();
}
/**
* Returns whether the @p profile has the blur setting enabled
*/
static bool profileHasBlurEnabled(const QExplicitlySharedDataPointer<Profile> &profile);
/** returns the active tab from the view
*/
TabbedViewContainer *activeContainer();
TerminalDisplay *createView(Session *session);
void attachView(TerminalDisplay *terminal, Session *session);
static std::shared_ptr<const ColorScheme> colorSchemeForProfile(const QExplicitlySharedDataPointer<Profile> &profile);
/** Reorder the terminal display history list */
void updateTerminalDisplayHistory(TerminalDisplay *terminalDisplay = nullptr, bool remove = false);
QHash<TerminalDisplay *, Session *> forgetAll(ViewSplitter *splitter);
Session *forgetTerminal(TerminalDisplay *terminal);
/**
* Creates and returns new session
*
* The session has specified @p profile, working @p directory
* and configured environment.
*/
Session *createSession(const QExplicitlySharedDataPointer<Profile> &profile, const QString &directory = QString());
Q_SIGNALS:
/** Emitted when the last view is removed from the view manager */
void empty();
/** Emitted when a session is detached from a view owned by this ViewManager */
void terminalsDetached(ViewSplitter *splitter, QHash<TerminalDisplay *, Session *> sessionsMap);
/**
* Emitted when the active view changes.
* @param controller The controller associated with the active view
*/
void activeViewChanged(SessionController *controller);
/**
* Emitted when the current session needs unplugged from factory().
* @param controller The controller associated with the active view
*/
void unplugController(SessionController *controller);
/**
* Emitted when the list of view properties ( as returned by viewProperties() ) changes.
* This occurs when views are added to or removed from the active container, or
* if the active container is changed.
*/
void viewPropertiesChanged(const QList<ViewProperties *> &propertiesList);
/**
* Emitted when menu bar visibility changes because a profile that requires so is
* activated.
*/
void setMenuBarVisibleRequest(bool);
void updateWindowIcon();
void blurSettingChanged(bool);
/** Requests creation of a new view with the default profile. */
void newViewRequest();
/** Requests creation of a new view, with the selected profile. */
void newViewWithProfileRequest(const QExplicitlySharedDataPointer<Profile> &profile);
void activationRequest(const QString &xdgActivationToken);
public Q_SLOTS:
/** DBus slot that returns the number of sessions in the current view. */
Q_SCRIPTABLE int sessionCount();
/**
* DBus slot that returns the unique ids of the sessions in the
* current view. The returned list is ordered by tab.
* QList<int> is not printable by qdbus so we use QStringList
* Example:
* A) create tab, create tab 2, create tab 3, go to tab 2, split view
* sessionList() returns 1 4 2 3
* B) create tab, create tab 2, split view, create tab 3
* sessionList() returns 1 3 2 4
*/
Q_SCRIPTABLE QStringList sessionList();
/** DBus slot that returns the current (active) session window */
Q_SCRIPTABLE int currentSession();
/** DBus slot that sets the current (active) session window */
Q_SCRIPTABLE void setCurrentSession(int sessionId);
/** DBus slot that creates a new session in the current view with the associated
* default profile and the default working directory
*/
Q_SCRIPTABLE int newSession();
/** DBus slot that creates a new session in the current view.
* @param profile the name of the profile to be used
* started.
*/
Q_SCRIPTABLE int newSession(const QString &profile);
/** DBus slot that creates a new session in the current view.
* @param profile the name of the profile to be used
* @param directory the working directory where the session is
* started.
*/
Q_SCRIPTABLE int newSession(const QString &profile, const QString &directory);
// TODO: its semantic is application-wide. Move it to more appropriate place
// DBus slot that returns the name of default profile
Q_SCRIPTABLE QString defaultProfile();
// TODO: its semantic is application-wide. Move it to more appropriate place
// DBus slot that sets the default profile
Q_SCRIPTABLE void setDefaultProfile(const QString &profile);
// TODO: its semantic is application-wide. Move it to more appropriate place
// DBus slot that returns a string list of defined (known) profiles
Q_SCRIPTABLE QStringList profileList();
/** DBus slot that changes the view port to the next session */
Q_SCRIPTABLE void nextSession();
/** DBus slot that changes the view port to the previous session */
Q_SCRIPTABLE void prevSession();
/** DBus slot that switches the current session (as returned by
* currentSession()) with the left (or previous) one in the
* navigation tab.
*/
Q_SCRIPTABLE void moveSessionLeft();
/** DBus slot that Switches the current session (as returned by
* currentSession()) with the right (or next) one in the navigation
* tab.
*/
Q_SCRIPTABLE void moveSessionRight();
/** DBus slot that sets ALL tabs' width to match their text */
Q_SCRIPTABLE void setTabWidthToText(bool);
// Creates json file with split config
Q_SCRIPTABLE void saveLayoutFile();
Q_SCRIPTABLE void loadLayoutFile();
Q_SCRIPTABLE void loadLayout(QString File);
/** DBus slot that returns a description of the layout hierarchy
* in each tab.
*
* A ViewSplitter is described by its id enclosed in round brackets,
* followed by square (horizontal split) or curly (vertical split)
* brackets. Format: (%id)[...] OR (%id){...}
*
* A TerminalDisplay is simply described by its id.
*
* The child widgets of a ViewSplitter are described in the square or
* curly brackets using their ids and are separated by '|'. The
* order which they are described in is top to bottom for vertical
* split or left to right for horizontal split.
*/
Q_SCRIPTABLE QStringList viewHierarchy();
/** DBus slot that returns the sizes of the direct child widgets as
* percentages of the size of the ViewSplitter in the form of a
* list. Will return an empty list if a splitter with id splitterId
* cannot be found.
*/
Q_SCRIPTABLE QList<double> getSplitProportions(int splitterId);
/** DBus slot that splits an existing view in the current tab **/
Q_SCRIPTABLE bool createSplit(int viewId, bool horizontalSplit);
/** DBus slot that creates a splitter containing a series of
* existing widgets and inserts it at an index in an existing splitter.
*
* Each element in widgetInfos is QString of format "x-y", describing
* each moved widget. Regarding the to-be-created splitter, the first
* QString will represent the leftmost widget (horizontal split)
* or the topmost widget (vertical split). x is either 's'
* for a ViewSplitter or 'v' for a TerminalDisplay. y is the id of
* the widget.
*/
Q_SCRIPTABLE bool createSplitWithExisting(int targetSplitterId, QStringList widgetInfos, int idx, bool horizontalSplit);
/** DBus slot that focuses a view. **/
Q_SCRIPTABLE bool setCurrentView(int viewId);
/** DBus slot that allows setting of the relative sizes of widgets
* in a splitter by specifying their percentages.
*/
/* NOTE: not all ways of calling dbus can handle "ad" QList<double>
Does not work AFAIK: qdbus6, qdbusviewer6, busctl
Works: dbus-send, gdbus
Example:
dbus-send --session --print-reply=literal --type=method_call --dest=org.kde.konsole-16710 /Windows/1 org.kde.konsole.Window.resizeSplits int32:0 array:double:40.0,60.0
gdbus call --session --dest org.kde.konsole-16710 --object-path /Windows/1 --method org.kde.konsole.Window.resizeSplits 0 "[10.5, 89.5]"
*/
Q_SCRIPTABLE bool resizeSplits(int splitterId, QList<double> percentages);
/** DBus slot that moves a splitter to a different position under a specified splitter**/
Q_SCRIPTABLE bool moveSplitter(int splitterId, int targetSplitterId, int idx);
/** DBus slot that move a view to a different position under a specified splitter**/
Q_SCRIPTABLE bool moveView(int viewId, int targetSplitterId, int idx);
private Q_SLOTS:
// called when the "Split View Left/Right" menu item is selected
void splitLeftRight();
void splitTopBottom();
void splitAuto(bool fromNextTab = false);
void splitLeftRightNextTab();
void splitTopBottomNextTab();
void splitAutoNextTab();
void expandActiveContainer();
void shrinkActiveContainer();
void equalSizeAllContainers();
// called when the "Detach View" menu item is selected
void detachActiveView();
void detachActiveTab();
// called when a session terminates - the view manager will delete any
// views associated with the session
void sessionFinished(Session *session);
// called when one view has been destroyed
void viewDestroyed(QWidget *view);
// controller detects when an associated view is given the focus
// and emits a signal. ViewManager listens for that signal
// and then plugs the action into the UI
// void viewFocused( SessionController* controller );
// called when the active view in a ViewContainer changes, so
// that we can plug the appropriate actions into the UI
void activateView(TerminalDisplay *view);
void focusUp();
void focusDown();
void focusLeft();
void focusRight();
// called when "Next View" shortcut is activated
void nextView();
// called when "Previous View" shortcut is activated
void previousView();
// called when "Switch to last tab" shortcut is activated
void lastView();
// called when "Switch to last used tab" shortcut is activated
void lastUsedView();
// called when "Switch to last used tab (reverse)" shortcut is activated
void lastUsedViewReverse();
// called when "Next View Container" shortcut is activated
void nextContainer();
// called when "Toggle Two tabs" shortcut is activated
void toggleTwoViews();
// called when the views in a container owned by this view manager
// changes
void containerViewsChanged(TabbedViewContainer *container);
// called when a profile changes
void profileChanged(const QExplicitlySharedDataPointer<Profile> &profile);
void updateViewsForSession(Session *session);
// moves active view to the left
void moveActiveViewLeft();
// moves active view to the right
void moveActiveViewRight();
// switches to the view at visual position 'index'
// in the current container
void switchToView(int index);
// gives focus and switches the terminal display, changing tab if needed
void switchToTerminalDisplay(TerminalDisplay *terminalDisplay);
// called when a SessionController gains focus
void controllerChanged(SessionController *controller);
/**
* Disconnect this ViewManager and MainWindow from @p controller
* and its associated view/session pair such as after a split-view
* has been drag-and-dropped to a new window.
*/
void forgetController(SessionController *controller);
/* Detaches the tab at index tabIdx */
void detachTab(int tabIdx);
void semanticSetupBash();
void toggleSemanticHints();
void toggleLineNumbers();
private:
Q_DISABLE_COPY(ViewManager)
TerminalDisplay *findTerminalDisplay(int viewId);
void setCurrentView(TerminalDisplay *view);
void createView(Session *session, TabbedViewContainer *container, int index);
void setupActions();
// takes a view from a view container owned by a different manager and places it in
// newContainer owned by this manager
void takeView(ViewManager *otherManager, TabbedViewContainer *otherContainer, TabbedViewContainer *newContainer, TerminalDisplay *view);
void splitView(Qt::Orientation orientation, bool fromNextTab = false);
// creates a new container which can hold terminal displays
TabbedViewContainer *createContainer();
// creates a new terminal display
// the 'session' is used so that the terminal display's random seed
// can be set to something which depends uniquely on that session
TerminalDisplay *createTerminalDisplay(Session *session = nullptr);
// creates a new controller for a session/display pair which provides the menu
// actions associated with that view, and exposes basic information
// about the session ( such as title and associated icon ) to the display.
SessionController *createController(Session *session, TerminalDisplay *view);
void removeController(SessionController *controller);
// Activates a different terminal when the TerminalDisplay
// closes or is detached and another one should be focused.
// It will activate the last used terminal within the same splitView
// if possible otherwise it will focus the last used tab
void focusAnotherTerminal(ViewSplitter *toplevelSplitter);
void activateLastUsedView(bool reverse);
void registerTerminal(TerminalDisplay *terminal);
void unregisterTerminal(TerminalDisplay *terminal);
private:
QPointer<TabbedViewContainer> _viewContainer;
QPointer<SessionController> _pluggedController;
QHash<TerminalDisplay *, Session *> _sessionMap;
KActionCollection *_actionCollection;
NavigationMethod _navigationMethod;
NavigationVisibility _navigationVisibility;
int _managerId;
static int lastManagerId;
QList<TerminalDisplay *> _terminalDisplayHistory;
int _terminalDisplayHistoryIndex;
// List of actions that should only be enabled when there are multiple view
// containers open
QList<QAction *> _multiTabOnlyActions;
QList<QAction *> _multiSplitterOnlyActions;
};
}
#endif
@@ -0,0 +1,109 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "ViewProperties.h"
using Konsole::ViewProperties;
QHash<int, ViewProperties *> ViewProperties::_viewProperties;
ViewProperties::ViewProperties(QObject *parent)
: QObject(parent)
, _icon(QIcon())
, _title(QString())
, _identifier(0)
{
}
ViewProperties::~ViewProperties()
{
_viewProperties.remove(_identifier);
}
ViewProperties *ViewProperties::propertiesById(int id)
{
return _viewProperties[id];
}
QUrl ViewProperties::url() const
{
return QUrl();
}
QString ViewProperties::currentDir() const
{
return QString();
}
void ViewProperties::fireActivity()
{
Q_EMIT activity(this);
}
void ViewProperties::rename()
{
}
void ViewProperties::setTitle(const QString &title)
{
if (title != _title) {
_title = title;
Q_EMIT titleChanged(this);
}
}
void ViewProperties::setIcon(const QIcon &icon)
{
// the icon's cache key is used to determine whether this icon is the same
// as the old one. if so no signal is emitted.
if (icon.cacheKey() != _icon.cacheKey()) {
_icon = icon;
Q_EMIT iconChanged(this);
}
}
void ViewProperties::setColor(const QColor &color)
{
if (color != _color) {
_color = color;
Q_EMIT colorChanged(this);
}
}
void ViewProperties::setIdentifier(int id)
{
if (_viewProperties.contains(_identifier)) {
_viewProperties.remove(_identifier);
}
_identifier = id;
_viewProperties.insert(id, this);
}
QString ViewProperties::title() const
{
return _title;
}
QIcon ViewProperties::icon() const
{
return _icon;
}
int ViewProperties::identifier() const
{
return _identifier;
}
QColor ViewProperties::color() const
{
return _color;
}
#include "moc_ViewProperties.cpp"
@@ -0,0 +1,135 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef VIEWPROPERTIES_H
#define VIEWPROPERTIES_H
// Qt
#include <QColor>
#include <QHash>
#include <QIcon>
#include <QObject>
#include <QUrl>
// Konsole
#include "konsoleprivate_export.h"
#include "session/Session.h"
namespace Konsole
{
/**
* Encapsulates user-visible information about the terminal session currently being displayed in a view,
* such as the associated title and icon.
*
* This can be used by navigation widgets in a ViewContainer sub-class to provide a tab, label or other
* item for switching between views.
*/
class KONSOLEPRIVATE_EXPORT ViewProperties : public QObject
{
Q_OBJECT
public:
explicit ViewProperties(QObject *parent);
~ViewProperties() override;
/** Returns the icon associated with a view */
QIcon icon() const;
/** Returns the title associated with a view */
QString title() const;
/** Returns the color associated with a view */
QColor color() const;
/**
* Returns the URL current associated with a view.
* The default implementation returns an empty URL.
*/
virtual QUrl url() const;
/**
* Returns the current directory associated with a view.
* This may be the same as url()
* The default implementation returns an empty string.
*/
virtual QString currentDir() const;
/**
* A unique identifier associated with this
* ViewProperties instance.
*/
int identifier() const;
/**
* Sub-classes may re-implement this method to display a message to the user
* to allow them to confirm whether to close a view.
* The default implementation always returns true
*/
virtual bool confirmClose() const
{
return true;
}
/** Finds a ViewProperties instance given its numeric identifier. */
static ViewProperties *propertiesById(int id);
Q_SIGNALS:
/** Emitted when the icon for a view changes */
void iconChanged(ViewProperties *properties);
/** Emitted when the title for a view changes */
void titleChanged(ViewProperties *properties);
/** Emitted when the color for a view changes */
void colorChanged(ViewProperties *properties);
/** Emitted when activity has occurred in this view. */
void activity(ViewProperties *item);
/** Emitted when notification for a view changes */
void notificationChanged(ViewProperties *item, Session::Notification notification, bool enabled);
/** Emitted when read only state changes */
void readOnlyChanged(ViewProperties *item);
/** Emitted when "copy input" state changes */
void copyInputChanged(ViewProperties *item);
public Q_SLOTS:
/**
* Requests the renaming of this view.
* The default implementation does nothing.
*/
virtual void rename();
protected Q_SLOTS:
/** Emits the activity() signal. */
void fireActivity();
protected:
/**
* Subclasses may call this method to change the title. This causes
* a titleChanged() signal to be emitted
*/
void setTitle(const QString &title);
/**
* Subclasses may call this method to change the icon. This causes
* an iconChanged() signal to be emitted
*/
void setIcon(const QIcon &icon);
/**
* Subclasses may call this method to change the color. This causes
* a colorChanged() signal to be emitted
*/
void setColor(const QColor &color);
/** Subclasses may call this method to change the identifier. */
void setIdentifier(int id);
private:
Q_DISABLE_COPY(ViewProperties)
QIcon _icon;
QString _title;
QColor _color;
int _identifier;
static QHash<int, ViewProperties *> _viewProperties;
};
}
#endif // VIEWPROPERTIES_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,323 @@
/*
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef VT102EMULATION_H
#define VT102EMULATION_H
// Qt
#include <QHash>
#include <QMap>
#include <QMediaPlayer>
#include <QPair>
#include <QVector>
#include <QList>
// Konsole
#include "Emulation.h"
#include "Screen.h"
#include "keyboardtranslator/KeyboardTranslator.h"
class QTimer;
class QKeyEvent;
#define MODE_AppCuKeys (MODES_SCREEN + 0) // Application cursor keys (DECCKM)
#define MODE_AppKeyPad (MODES_SCREEN + 1) //
#define MODE_Mouse1000 (MODES_SCREEN + 2) // Send mouse X,Y position on press and release
#define MODE_Mouse1001 (MODES_SCREEN + 3) // Use Highlight mouse tracking
#define MODE_Mouse1002 (MODES_SCREEN + 4) // Use cell motion mouse tracking
#define MODE_Mouse1003 (MODES_SCREEN + 5) // Use all motion mouse tracking
#define MODE_Mouse1005 (MODES_SCREEN + 6) // Xterm-style extended coordinates
#define MODE_Mouse1006 (MODES_SCREEN + 7) // 2nd Xterm-style extended coordinates
#define MODE_Mouse1007 (MODES_SCREEN + 8) // XTerm Alternate Scroll mode; also check AlternateScrolling profile property
#define MODE_Mouse1015 (MODES_SCREEN + 9) // Urxvt-style extended coordinates
#define MODE_Ansi (MODES_SCREEN + 10) // Use US Ascii for character sets G0-G3 (DECANM)
#define MODE_132Columns (MODES_SCREEN + 11) // 80 <-> 132 column mode switch (DECCOLM)
#define MODE_Allow132Columns (MODES_SCREEN + 12) // Allow DECCOLM mode
#define MODE_BracketedPaste (MODES_SCREEN + 13) // Xterm-style bracketed paste mode
#define MODE_Sixel (MODES_SCREEN + 14) // Xterm-style bracketed paste mode
#define MODE_total (MODES_SCREEN + 15)
namespace Konsole
{
extern unsigned short vt100_graphics[32];
struct CharCodes {
// coding info
char charset[4]; //
int cu_cs; // actual charset.
bool graphic; // Some VT100 tricks
bool pound; // Some VT100 tricks
bool sa_graphic; // saved graphic
bool sa_pound; // saved pound
};
/**
* Provides an xterm compatible terminal emulation based on the DEC VT102 terminal.
* A full description of this terminal can be found at https://vt100.net/docs/vt102-ug/
*
* In addition, various additional xterm escape sequences are supported to provide
* features such as mouse input handling.
* See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html for a description of xterm's escape
* sequences.
*
*/
class KONSOLEPRIVATE_EXPORT Vt102Emulation : public Emulation
{
Q_OBJECT
public:
/** Constructs a new emulation */
Vt102Emulation();
~Vt102Emulation() override;
// reimplemented from Emulation
void clearEntireScreen() override;
void reset(bool softReset = false, bool preservePrompt = false) override;
char eraseChar() const override;
public Q_SLOTS:
// reimplemented from Emulation
void sendString(const QByteArray &string) override;
void sendText(const QString &text) override;
void sendKeyEvent(QKeyEvent *) override;
void sendMouseEvent(int buttons, int column, int line, int eventType) override;
void focusChanged(bool focused) override;
void clearHistory() override;
protected:
// reimplemented from Emulation
void setMode(int mode) override;
void resetMode(int mode) override;
void receiveChars(const QVector<uint> &chars) override;
private Q_SLOTS:
// Causes sessionAttributeChanged() to be emitted for each (int,QString)
// pair in _pendingSessionAttributesUpdates.
// Used to buffer multiple attribute updates in the current session
void updateSessionAttributes();
void deletePlayer(QMediaPlayer::MediaStatus);
private:
unsigned int applyCharset(uint c);
void setCharset(int n, int cs);
void useCharset(int n);
void setAndUseCharset(int n, int cs);
void saveCursor();
void restoreCursor();
void resetCharset(int scrno);
void setMargins(int top, int bottom);
// set margins for all screens back to their defaults
void setDefaultMargins();
// returns true if 'mode' is set or false otherwise
bool getMode(int mode);
// saves the current boolean value of 'mode'
void saveMode(int mode);
// restores the boolean value of 'mode'
void restoreMode(int mode);
// resets all modes
// (except MODE_Allow132Columns)
void resetModes();
void resetTokenizer();
#define MAX_TOKEN_LENGTH 256 // Max length of tokens (e.g. window title)
void addToCurrentToken(uint cc);
int tokenBufferPos;
protected:
QList<char32_t> tokenBuffer;
private:
#define MAXARGS 16
void addDigit(int dig);
void addArgument();
void addSub();
struct subParam {
int value[MAXARGS]; // value[0] unused, it would correspond to the containing param value
int count;
};
struct {
int value[MAXARGS];
struct subParam sub[MAXARGS];
int count;
bool hasSubParams;
} params = {};
void initTokenizer();
enum ParserStates {
Ground,
Escape,
EscapeIntermediate,
CsiEntry,
CsiParam,
CsiIntermediate,
CsiIgnore,
DcsEntry,
DcsParam,
DcsIntermediate,
DcsPassthrough,
DcsIgnore,
OscString,
SosPmApcString,
Vt52Escape,
Vt52CupRow,
Vt52CupColumn,
};
enum {
Sos,
Pm,
Apc,
} _sosPmApc;
enum osc {
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands
ReportColors = 4,
ResetColors = 104,
// https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md
SemanticPrompts = 133,
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/doc/ControlSequences.md#OSC
Notification = 777,
Image = 1337,
};
ParserStates _state = Ground;
bool _ignore = false;
int _nIntermediate = 0;
unsigned char _intermediate[1];
// Used to get expected behaviour in emulated up/down movement in REPL mode.
int _targetCol = -1;
void switchState(const ParserStates newState, const uint cc);
void esc_dispatch(const uint cc);
void clear();
void collect(const uint cc);
void param(const uint cc);
void csi_dispatch(const uint cc);
void osc_start();
void osc_put(const uint cc);
void osc_end(const uint cc);
void hook(const uint cc);
void unhook();
void put(const uint cc);
void apc_start(const uint cc);
void apc_put(const uint cc);
void apc_end();
// State machine for escape sequences containing large amount of data
int tokenState;
const char *tokenStateChange;
int tokenPos;
QByteArray tokenData;
// Set of flags for each of the ASCII characters which indicates
// what category they fall into (printable character, control, digit etc.)
// for the purposes of decoding terminal output
int charClass[256];
QByteArray imageData;
quint32 imageId;
QMap<char, qint64> savedKeys;
protected:
virtual void reportDecodingError(int token);
virtual void processToken(int code, int p, int q);
virtual void processSessionAttributeRequest(const int tokenSize, const uint terminator);
virtual void processChecksumRequest(int argc, int argv[]);
private:
void processGraphicsToken(int tokenSize);
void sendGraphicsReply(const QString &params, const QString &error);
void reportTerminalType();
void reportTertiaryAttributes();
void reportSecondaryAttributes();
void reportVersion();
void reportStatus();
void reportAnswerBack();
void reportCursorPosition();
void reportPixelSize();
void reportCellSize();
void iTermReportCellSize();
void reportSize();
void reportColor(int c, QColor color);
void reportTerminalParms(int p);
void emulateUpDown(int up, KeyboardTranslator::Entry entry, QByteArray &textToSend, int toCol = -1);
// clears the screen and resizes it to the specified
// number of columns
void clearScreenAndSetColumns(int columnCount);
CharCodes _charset[2];
class TerminalState
{
public:
// Initializes all modes to false
TerminalState()
{
memset(&mode, 0, MODE_total * sizeof(bool));
}
bool mode[MODE_total];
};
TerminalState _currentModes;
TerminalState _savedModes;
// Hash table and timer for buffering calls to update certain session
// attributes (e.g. the name of the session, window title).
// These calls occur when certain escape sequences are detected in the
// output from the terminal. See Emulation::sessionAttributeChanged()
QHash<int, QString> _pendingSessionAttributesUpdates;
QTimer *_sessionAttributesUpdateTimer;
bool _reportFocusEvents;
QColor colorTable[256];
// Sixel:
#define MAX_SIXEL_COLORS 256
#define MAX_IMAGE_DIM 16384
void sixelQuery(int query);
bool processSixel(uint cc);
void SixelModeEnable(int width, int height /*, bool preserveBackground*/);
void SixelModeAbort();
void SixelModeDisable();
void SixelColorChangeRGB(const int index, int red, int green, int blue);
void SixelColorChangeHSL(const int index, int hue, int saturation, int value);
void SixelCharacterAdd(uint8_t character, int repeat = 1);
bool m_SixelPictureDefinition = false;
bool m_SixelStarted = false;
QImage m_currentImage;
int m_currentX = 0;
int m_verticalPosition = 0;
uint8_t m_currentColor = 0;
bool m_preserveBackground = true;
QPair<int, int> m_aspect = qMakePair(1, 1);
bool m_SixelScrolling = true;
QSize m_actualSize; // For efficiency reasons, we keep the image in memory larger than what the end result is
// Kitty
QHash<int, QPixmap> _graphicsImages;
// For kitty graphics protocol - image cache
int getFreeGraphicsImageId();
QMediaPlayer *player;
};
}
#endif // VT102EMULATION_H
@@ -0,0 +1,30 @@
/*
SPDX-FileCopyrightText: 2012 Jekyll Wu <adaptee@gmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
// Own
#include "WindowSystemInfo.h"
#include "config-konsole.h"
#include <QtGlobal>
#if WITH_X11
#include <KWindowSystem>
#include <KX11Extras>
#endif
using Konsole::WindowSystemInfo;
bool WindowSystemInfo::HAVE_TRANSPARENCY = false;
bool WindowSystemInfo::compositingActive()
{
#if WITH_X11
return !KWindowSystem::isPlatformX11() || KX11Extras::compositingActive();
#else
return true;
#endif
}
@@ -0,0 +1,23 @@
/*
SPDX-FileCopyrightText: 2012 Jekyll Wu <adaptee@gmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef WINDOWSYSTEMINFO_H
#define WINDOWSYSTEMINFO_H
#include "konsoleprivate_export.h"
namespace Konsole
{
class KONSOLEPRIVATE_EXPORT WindowSystemInfo
{
public:
static bool HAVE_TRANSPARENCY;
static bool compositingActive();
};
}
#endif // WINDOWSYSTEMINFO_H
@@ -0,0 +1,87 @@
/*
* SPDX-FileCopyrightText: 2002 Waldo Bastian <bastian@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-only
**/
// Own
#include "ZModemDialog.h"
// KDE
#include <KTextEdit>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
using Konsole::ZModemDialog;
ZModemDialog::ZModemDialog(QWidget *aParent, bool modal, const QString &caption)
: QDialog(aParent)
, _textEdit(nullptr)
, mButtonBox(nullptr)
{
setObjectName(QStringLiteral("zmodem_progress"));
setModal(modal);
setWindowTitle(caption);
mButtonBox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Close);
auto mainWidget = new QWidget(this);
auto mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
mainLayout->addWidget(mButtonBox);
// Use Cancel here to stop the transfer
mButtonBox->button(QDialogButtonBox::Cancel)->setEnabled(true);
mButtonBox->button(QDialogButtonBox::Close)->setEnabled(false);
connect(mButtonBox, &QDialogButtonBox::rejected, this, &Konsole::ZModemDialog::slotCancel);
connect(mButtonBox, &QDialogButtonBox::accepted, this, &Konsole::ZModemDialog::slotClose);
_textEdit = new KTextEdit(this);
_textEdit->setMinimumSize(400, 100);
_textEdit->setReadOnly(true);
mainLayout->addWidget(_textEdit);
addText(QStringLiteral("Note: pressing Cancel will almost certainly cause the terminal to be unusable."));
addText(QStringLiteral("-----------------"));
}
void ZModemDialog::addText(const QString &text)
{
_textEdit->append(text);
}
void ZModemDialog::addProgressText(const QString &text)
{
_textEdit->insertPlainText(text);
}
void ZModemDialog::slotCancel()
{
Q_EMIT zmodemCancel();
slotClose();
}
void ZModemDialog::transferDone()
{
mButtonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
mButtonBox->button(QDialogButtonBox::Close)->setEnabled(true);
}
void ZModemDialog::slotClose()
{
delayedDestruct();
accept();
}
void ZModemDialog::delayedDestruct()
{
if (isVisible()) {
hide();
}
deleteLater();
}
#include "moc_ZModemDialog.cpp"
@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2002 Waldo Bastian <bastian@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-only
**/
#ifndef ZMODEM_DIALOG_H
#define ZMODEM_DIALOG_H
#include <QDialog>
#include "konsoleprivate_export.h"
class KTextEdit;
class QDialogButtonBox;
namespace Konsole
{
class KONSOLEPRIVATE_EXPORT ZModemDialog : public QDialog
{
Q_OBJECT
public:
ZModemDialog(QWidget *parent, bool modal, const QString &caption);
/**
* Adds a line of text to the progress window
*/
void addText(const QString &);
/**
* Adds a line of text without a new line to the progress window
*/
void addProgressText(const QString &);
/**
* To indicate the process is finished.
*/
void transferDone();
Q_SIGNALS:
void zmodemCancel();
private Q_SLOTS:
void slotClose();
void slotCancel();
private:
Q_DISABLE_COPY(ZModemDialog)
void delayedDestruct();
KTextEdit *_textEdit;
QDialogButtonBox *mButtonBox;
};
}
#endif
@@ -0,0 +1,76 @@
/*
SPDX-FileCopyrightText: 2019 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "BookMarkTest.h"
// Qt
#include <QTest>
// KDE
#include <KBookmarkManager>
using namespace Konsole;
/* This does not use Konsole's BookmarkHandler directly; it is used to
* test the code copied from there and to test any changes.
*/
/* Test that the URL (command) does not get mangled by KBookMark's encoding */
void BookMarkTest::testBookMarkURLs_data()
{
auto testData = QFINDTESTDATA(QStringLiteral("data/bookmarks.xml"));
auto bookmarkManager = std::make_unique<KBookmarkManager>(testData);
auto groupUrlList = bookmarkManager->root().groupUrlList();
// text explaining test, correct test result
QStringList bm_urls = {QStringLiteral("simple command"),
QStringLiteral("ssh machine"),
QStringLiteral("command with pipe (|)"),
QStringLiteral("ssh machine | tee -a /var/log/system.log"),
QStringLiteral("file URL w/ non ASCII part"),
QStringLiteral("file:///home/user/aκόσμε"),
QStringLiteral("command with double quotes"),
QStringLiteral("isql-fb -u sysdba -p example \"test\""),
QStringLiteral("command with single quotes"),
QStringLiteral("isql-fb -u sysdba -p example 'test'"),
QStringLiteral("command with %"),
QStringLiteral("date +%m-%d-%Y")};
QTest::addColumn<QString>("text");
QTest::addColumn<QString>("result");
for (int i = 0; i < groupUrlList.size(); ++i) {
const auto &bm_url = groupUrlList.at(i);
// Verify this line matching SessionControll.cpp to validate tests
auto bm = QUrl::fromPercentEncoding(bm_url.toEncoded());
QTest::newRow(bm_urls.at(i * 2).toUtf8().data()) << bm_urls.at((i * 2) + 1) << bm;
}
}
// Only test top level URLs (no folders)
void BookMarkTest::testBookMarkURLs()
{
QFETCH(QString, text);
QFETCH(QString, result);
QCOMPARE(text, result);
}
/*
* When testing more than just URLs, the below loop can be used
auto bm = root.first();
int i = 0;
while (!bm.isNull()) {
auto bm_url = QUrl::fromPercentEncoding(bm.url().toString().toUtf8());
qWarning()<< i << ":" <<bm.url().toString();
i++;
bm = root.next(bm);
}
*/
QTEST_GUILESS_MAIN(BookMarkTest)
#include "moc_BookMarkTest.cpp"
@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2019 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BOOKMARKTEST_H
#define BOOKMARKTEST_H
#include <QObject>
namespace Konsole
{
class BookMarkTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testBookMarkURLs_data();
void testBookMarkURLs();
private:
};
}
#endif // BOOKMARKTEST_H
@@ -0,0 +1,47 @@
include(ECMAddTests)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR})
set(KONSOLE_TEST_LIBS Qt::Test konsoleprivate konsolecharacters konsoledecoders)
# Temporary bypass for BKO 432379
if(BUILD_SHARED_LIBS)
ecm_add_test(KeyboardTranslatorTest.cpp LINK_LIBRARIES ${KONSOLE_TEST_LIBS})
endif()
if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT WIN32)
ecm_add_test(
PartTest.cpp
LINK_LIBRARIES KF6::XmlGui KF6::Parts KF6::Pty ${KONSOLE_TEST_LIBS}
)
endif()
ecm_add_tests(
BookMarkTest.cpp
CharacterColorTest.cpp
CharacterTest.cpp
CharacterWidthTest.cpp
HotSpotFilterTest.cpp
ProcessInfoTest.cpp
ProfileTest.cpp
ScreenTest.cpp
ShellCommandTest.cpp
TerminalCharacterDecoderTest.cpp
Vt102EmulationTest.cpp
LINK_LIBRARIES ${KONSOLE_TEST_LIBS}
)
if(NOT WIN32)
ecm_add_tests(
PtyTest.cpp
LINK_LIBRARIES KF6::Pty ${KONSOLE_TEST_LIBS}
)
endif()
ecm_add_tests(
HistoryTest.cpp
SessionTest.cpp
TerminalInterfaceTest.cpp
TerminalTest.cpp
LINK_LIBRARIES ${KONSOLE_TEST_LIBS} KF6::Parts
)
@@ -0,0 +1,199 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "CharacterColorTest.h"
// Qt
// KDE
#include <QTest>
using namespace Konsole;
const QColor CharacterColorTest::DefaultColorTable[TABLE_COLORS] = {
QColor(0x00, 0x00, 0x00), // Dfore
QColor(0xFF, 0xFF, 0xFF), // Dback
QColor(0x00, 0x00, 0x00), // Black
QColor(0xB2, 0x18, 0x18), // Red
QColor(0x18, 0xB2, 0x18), // Green
QColor(0xB2, 0x68, 0x18), // Yellow
QColor(0x18, 0x18, 0xB2), // Blue
QColor(0xB2, 0x18, 0xB2), // Magenta
QColor(0x18, 0xB2, 0xB2), // Cyan
QColor(0xB2, 0xB2, 0xB2), // White
// intensive versions
QColor(0x00, 0x00, 0x00),
QColor(0xFF, 0xFF, 0xFF),
QColor(0x68, 0x68, 0x68),
QColor(0xFF, 0x54, 0x54),
QColor(0x54, 0xFF, 0x54),
QColor(0xFF, 0xFF, 0x54),
QColor(0x54, 0x54, 0xFF),
QColor(0xFF, 0x54, 0xFF),
QColor(0x54, 0xFF, 0xFF),
QColor(0xFF, 0xFF, 0xFF),
// Here are faint intensities, which may not be good.
// faint versions
QColor(0x00, 0x00, 0x00),
QColor(0xFF, 0xFF, 0xFF),
QColor(0x00, 0x00, 0x00),
QColor(0x65, 0x00, 0x00),
QColor(0x00, 0x65, 0x00),
QColor(0x65, 0x5E, 0x00),
QColor(0x00, 0x00, 0x65),
QColor(0x65, 0x00, 0x65),
QColor(0x00, 0x65, 0x65),
QColor(0x65, 0x65, 0x65),
};
void CharacterColorTest::init()
{
}
void CharacterColorTest::cleanup()
{
}
void CharacterColorTest::testColorEntry()
{
QColor black = QColor(0x00, 0x00, 0x00);
QColor white = QColor(0xFF, 0xFF, 0xFF);
QColor red = QColor(0xB2, 0x18, 0x18);
QColor green = QColor(0x18, 0xB2, 0x18);
// Test operator== operator!=
QCOMPARE(black == white, false);
QCOMPARE(black != white, true);
QCOMPARE(red == green, false);
QCOMPARE(red != green, true);
QCOMPARE(red == green, false);
QCOMPARE(red != green, true);
// Test operator=
QColor tmpColorEntry;
tmpColorEntry = red;
QCOMPARE(tmpColorEntry == red, true);
QCOMPARE(red == tmpColorEntry, true);
// Test QColor()
QColor defaultColorEntry = QColor();
QCOMPARE(defaultColorEntry != green, true);
QCOMPARE(defaultColorEntry != black, true);
QCOMPARE(defaultColorEntry.isValid(), false);
}
void CharacterColorTest::testDummyConstructor()
{
CharacterColor charColor;
QCOMPARE(charColor.isValid(), false);
}
void CharacterColorTest::testColorSpaceDefault_data()
{
QTest::addColumn<int>("colorValue");
QTest::addColumn<QColor>("expected");
QTest::newRow("color 0") << 0 << DefaultColorTable[0];
QTest::newRow("color 1") << 1 << DefaultColorTable[1];
}
void CharacterColorTest::testColorSpaceDefault()
{
QFETCH(int, colorValue);
QFETCH(QColor, expected);
CharacterColor charColor(COLOR_SPACE_DEFAULT, colorValue);
const QColor result = charColor.color(DefaultColorTable);
QCOMPARE(result, expected);
}
void CharacterColorTest::testColorSpaceSystem_data()
{
QTest::addColumn<int>("colorValue");
QTest::addColumn<QColor>("expected");
QTest::newRow("color 0") << 0 << DefaultColorTable[2 + 0];
QTest::newRow("color 1") << 1 << DefaultColorTable[2 + 1];
QTest::newRow("color 7") << 7 << DefaultColorTable[2 + 7];
}
void CharacterColorTest::testColorSpaceSystem()
{
QFETCH(int, colorValue);
QFETCH(QColor, expected);
CharacterColor charColor(COLOR_SPACE_SYSTEM, colorValue);
const QColor result = charColor.color(DefaultColorTable);
QCOMPARE(result, expected);
}
void CharacterColorTest::testColorSpaceRGB_data()
{
QTest::addColumn<int>("colorValue");
QTest::addColumn<QColor>("expected");
// Pick colors to test or test all if needed
for (const int i : {0, 1, 64, 127, 128, 215, 255}) {
const QString name = QStringLiteral("color %1").arg(i);
QTest::newRow(qPrintable(name)) << i << QColor(i >> 16, i >> 8, i);
}
}
void CharacterColorTest::testColorSpaceRGB()
{
QFETCH(int, colorValue);
QFETCH(QColor, expected);
CharacterColor charColor(COLOR_SPACE_RGB, colorValue);
const QColor result = charColor.color(DefaultColorTable);
QCOMPARE(result, expected);
}
void CharacterColorTest::testColor256_data()
{
QTest::addColumn<int>("colorValue");
QTest::addColumn<QColor>("expected");
// This might be overkill
for (int i = 0; i < 8; ++i) {
const QString name = QStringLiteral("color256 color %1").arg(i);
QTest::newRow(qPrintable(name)) << i << DefaultColorTable[i + 2];
}
for (int i = 8; i < 16; ++i) {
const QString name = QStringLiteral("color256 color %1").arg(i);
QTest::newRow(qPrintable(name)) << i << DefaultColorTable[i + 2 + 10 - 8];
}
for (int i = 16; i < 232; ++i) {
const QString name = QStringLiteral("color256 color %1").arg(i);
const auto u = i - 16;
const auto color = QColor(((u / 36) % 6) != 0 ? (40 * ((u / 36) % 6) + 55) : 0,
((u / 6) % 6) != 0 ? (40 * ((u / 6) % 6) + 55) : 0,
((u / 1) % 6) != 0 ? (40 * ((u / 1) % 6) + 55) : 0);
QTest::newRow(qPrintable(name)) << i << color;
}
for (int i = 232; i < 256; ++i) {
const QString name = QStringLiteral("color256 color %1").arg(i);
const auto gray = (i - 232) * 10 + 8;
QTest::newRow(qPrintable(name)) << i << QColor(gray, gray, gray);
}
}
void CharacterColorTest::testColor256()
{
QFETCH(int, colorValue);
QFETCH(QColor, expected);
const QColor result = color256(colorValue, DefaultColorTable);
QCOMPARE(result, expected);
}
QTEST_GUILESS_MAIN(CharacterColorTest)
#include "moc_CharacterColorTest.cpp"
@@ -0,0 +1,42 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef CHARACTERCOLORTEST_H
#define CHARACTERCOLORTEST_H
#include <QObject>
#include "../characters/CharacterColor.h"
namespace Konsole
{
class CharacterColorTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void init();
void cleanup();
void testColorEntry();
void testDummyConstructor();
void testColorSpaceDefault_data();
void testColorSpaceDefault();
void testColorSpaceSystem_data();
void testColorSpaceSystem();
void testColorSpaceRGB_data();
void testColorSpaceRGB();
void testColor256_data();
void testColor256();
private:
static const QColor DefaultColorTable[];
};
}
#endif // CHARACTERCOLORTEST_H
@@ -0,0 +1,15 @@
/*
SPDX-FileCopyrightText: 2019 Tomaz Canabrava <tomaz.canabrava@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "CharacterTest.h"
#include "Character.h"
#include <QTest>
#include <cstdint>
QTEST_GUILESS_MAIN(Konsole::CharacterTest)
#include "moc_CharacterTest.cpp"
@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2019 Tomaz Canabrava <tomaz.canabrava@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QObject>
namespace Konsole
{
class CharacterTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
};
}
@@ -0,0 +1,67 @@
/*
SPDX-FileCopyrightText: 2014 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "CharacterWidthTest.h"
// KDE
#include <QTest>
#include "../characters/Character.h"
#include "konsoleprivate_export.h"
using namespace Konsole;
void CharacterWidthTest::testWidth_data()
{
QTest::addColumn<uint>("character");
QTest::addColumn<int>("width");
/* This is a work in progress.... */
/* konsole_wcwidth uses -1 C0/C1 and DEL */
QTest::newRow("0x007F") << uint(0x007F) << -1;
QTest::newRow("0x0300") << uint(0x0300) << 0;
QTest::newRow("0x070F") << uint(0x070F) << 0;
QTest::newRow("0x1160") << uint(0x1160) << 0;
QTest::newRow("0x10300") << uint(0x10300) << 1;
QTest::newRow("a") << uint('a') << 1;
QTest::newRow("0x00AD") << uint(0x00AD) << 1;
QTest::newRow("0x00A0") << uint(0x00A0) << 1;
QTest::newRow("0x10FB") << uint(0x10FB) << 1;
QTest::newRow("0xFF76") << uint(0xFF76) << 1;
QTest::newRow("0x103A0") << uint(0x103A0) << 1;
QTest::newRow("0x10A06") << uint(0x10A06) << 0;
QTest::newRow("0x3000") << uint(0x3000) << 2;
QTest::newRow("0x300a") << uint(0x300a) << 2;
QTest::newRow("0x300b") << uint(0x300b) << 2;
QTest::newRow("0xFF01") << uint(0xFF01) << 2;
QTest::newRow("0xFF5F") << uint(0xFF5F) << 2;
QTest::newRow("0xFF60") << uint(0xFF60) << 2;
QTest::newRow("0xFFe0") << uint(0xFFe6) << 2;
QTest::newRow("0x1F943 tumbler glass") << uint(0x1F943) << 2;
QTest::newRow("0x1F944 spoon") << uint(0x1F944) << 2;
QTest::newRow("0x26A1 high voltage sign (BUG 378124)") << uint(0x026A1) << 2;
QTest::newRow("0x2615 hot beverage (BUG 392171)") << uint(0x02615) << 2;
QTest::newRow("0x26EA church (BUG 392171)") << uint(0x026EA) << 2;
QTest::newRow("0x1D11E musical symbol g clef (BUG 339439)") << uint(0x1D11E) << 1;
}
void CharacterWidthTest::testWidth()
{
QFETCH(uint, character);
QTEST(Character::width(character), "width");
}
QTEST_GUILESS_MAIN(CharacterWidthTest)
#include "moc_CharacterWidthTest.cpp"
@@ -0,0 +1,26 @@
/*
SPDX-FileCopyrightText: 2014 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef CHARACTERWIDTHTEST_H
#define CHARACTERWIDTHTEST_H
#include <QObject>
namespace Konsole
{
class CharacterWidthTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testWidth_data();
void testWidth();
};
}
#endif // CHARACTERWIDTHTEST_H
@@ -0,0 +1,344 @@
/*
SPDX-FileCopyrightText: 2010 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "DBusTest.h"
#include "../profile/Profile.h"
#include "../profile/ProfileWriter.h"
#include "../session/Session.h"
// Qt
#include <QProcess>
#include <QRandomGenerator>
#include <QSignalSpy>
#include <QStandardPaths>
using namespace Konsole;
/* Exec a new Konsole and grab its dbus */
void DBusTest::initTestCase()
{
qRegisterMetaType<QProcess::ExitStatus>();
const QString interfaceName = QStringLiteral("org.kde.konsole");
QDBusConnectionInterface *bus = nullptr;
QStringList konsoleServices;
if (!QDBusConnection::sessionBus().isConnected() || ((bus = QDBusConnection::sessionBus().interface()) == nullptr)) {
QFAIL("Session bus not found");
}
QDBusReply<QStringList> serviceReply = bus->registeredServiceNames();
QVERIFY2(serviceReply.isValid(), "SessionBus interfaces not available");
// Find all current Konsoles' services running
QStringList allServices = serviceReply;
for (QStringList::const_iterator it = allServices.constBegin(), end = allServices.constEnd(); it != end; ++it) {
const QString service = *it;
if (service.startsWith(interfaceName)) {
konsoleServices << service;
}
}
// Create test profile
auto profile = Profile::Ptr(new Profile());
ProfileWriter profileWriter;
do {
_testProfileName = QStringLiteral("konsole-dbus-test-profile-%1").arg(QRandomGenerator::global()->generate());
profile->setProperty(Profile::UntranslatedName, _testProfileName);
profile->setProperty(Profile::Name, _testProfileName);
_testProfilePath = profileWriter.getPath(profile);
} while (QFile::exists(_testProfilePath));
_testProfileEnv = QStringLiteral("TEST_PROFILE=%1").arg(_testProfileName);
profile->setProperty(Profile::Environment, QStringList(_testProfileEnv));
// %D = Current Directory (Long) - hacky way to check current directory
profile->setProperty(Profile::LocalTabTitleFormat, QStringLiteral("%D"));
profileWriter.writeProfile(_testProfilePath, profile);
// Create a new Konsole with a separate process id
_process = new QProcess;
_process->setProcessChannelMode(QProcess::ForwardedChannels);
_process->start(QStringLiteral("konsole"), QStringList(QStringLiteral("--separate")));
if (!_process->waitForStarted()) {
QFAIL(QStringLiteral("Unable to exec a new Konsole").toLatin1().data());
}
// Wait for above Konsole to finish starting
QThread::sleep(5);
serviceReply = bus->registeredServiceNames();
QVERIFY2(serviceReply.isValid(), "SessionBus interfaces not available");
// Find dbus service of above Konsole
allServices = serviceReply;
for (QStringList::const_iterator it = allServices.constBegin(), end = allServices.constEnd(); it != end; ++it) {
const QString service = *it;
if (service.startsWith(interfaceName)) {
if (!konsoleServices.contains(service)) {
_interfaceName = service;
}
}
}
QVERIFY2(!_interfaceName.isEmpty(), "This test will only work in a Konsole window with a new PID. A new Konsole PID can't be found.");
QDBusInterface iface(_interfaceName, QStringLiteral("/Windows/1"), QStringLiteral("org.kde.konsole.Konsole"));
QVERIFY(iface.isValid());
}
/* Close the Konsole window that was opened to test the DBus interface
*/
void DBusTest::cleanupTestCase()
{
// Remove test profile
QFile::remove(_testProfilePath);
// Need to take care of when user has CloseAllTabs=False otherwise
// they will get a popup dialog when we try to close this.
QSignalSpy quitSpy(_process, SIGNAL(finished(int, QProcess::ExitStatus)));
// Do not use QWidget::close(), as it shows question popup when
// CloseAllTabs is set to false (default).
QDBusInterface iface(_interfaceName, QStringLiteral("/MainApplication"), QStringLiteral("org.qtproject.Qt.QCoreApplication"));
QVERIFY2(iface.isValid(), "Unable to get a dbus interface to Konsole!");
QDBusReply<void> instanceReply = iface.call(QStringLiteral("quit"));
if (!instanceReply.isValid()) {
QFAIL(QStringLiteral("Unable to close Konsole: %1").arg(instanceReply.error().message()).toLatin1().data());
}
quitSpy.wait();
_process->kill();
_process->deleteLater();
}
void DBusTest::testSessions()
{
QDBusReply<void> voidReply;
QDBusReply<bool> boolReply;
QDBusReply<QByteArray> arrayReply;
QDBusReply<QString> stringReply;
QDBusReply<QStringList> listReply;
QDBusInterface iface(_interfaceName, QStringLiteral("/Sessions/1"), QStringLiteral("org.kde.konsole.Session"));
QVERIFY(iface.isValid());
//****************** Test is/set MonitorActivity
voidReply = iface.call(QStringLiteral("setMonitorActivity"), false);
QVERIFY(voidReply.isValid());
boolReply = iface.call(QStringLiteral("isMonitorActivity"));
QVERIFY(boolReply.isValid());
QCOMPARE(boolReply.value(), false);
voidReply = iface.call(QStringLiteral("setMonitorActivity"), true);
QVERIFY(voidReply.isValid());
boolReply = iface.call(QStringLiteral("isMonitorActivity"));
QVERIFY(boolReply.isValid());
QCOMPARE(boolReply.value(), true);
//****************** Test is/set MonitorSilence
voidReply = iface.call(QStringLiteral("setMonitorSilence"), false);
QVERIFY(voidReply.isValid());
boolReply = iface.call(QStringLiteral("isMonitorSilence"));
QVERIFY(boolReply.isValid());
QCOMPARE(boolReply.value(), false);
voidReply = iface.call(QStringLiteral("setMonitorSilence"), true);
QVERIFY(voidReply.isValid());
boolReply = iface.call(QStringLiteral("isMonitorSilence"));
QVERIFY(boolReply.isValid());
QCOMPARE(boolReply.value(), true);
//****************** Test codec and setCodec
arrayReply = iface.call(QStringLiteral("codec"));
QVERIFY(arrayReply.isValid());
// Obtain a list of system's Codecs
QList<QByteArray> availableCodecs = QTextCodec::availableCodecs();
for (int i = 0; i < availableCodecs.count(); ++i) {
boolReply = iface.call(QStringLiteral("setCodec"), availableCodecs[i]);
QVERIFY(boolReply.isValid());
QCOMPARE(boolReply.value(), true);
arrayReply = iface.call(QStringLiteral("codec"));
QVERIFY(arrayReply.isValid());
// Compare result with name due to aliases issue
// Better way to do this?
QCOMPARE((QTextCodec::codecForName(arrayReply.value()))->name(), (QTextCodec::codecForName(availableCodecs[i]))->name());
}
//****************** Test is/set flowControlEnabled
voidReply = iface.call(QStringLiteral("setFlowControlEnabled"), true);
QVERIFY(voidReply.isValid());
boolReply = iface.call(QStringLiteral("flowControlEnabled"));
QVERIFY(boolReply.isValid());
QCOMPARE(boolReply.value(), true);
voidReply = iface.call(QStringLiteral("setFlowControlEnabled"), false);
QVERIFY(voidReply.isValid());
boolReply = iface.call(QStringLiteral("flowControlEnabled"));
QVERIFY(boolReply.isValid());
QCOMPARE(boolReply.value(), false);
//****************** Test is/set environment
listReply = iface.call(QStringLiteral("environment"));
QVERIFY(listReply.isValid());
QStringList prevEnv = listReply.value();
// for (int i = 0; i < prevEnv.size(); ++i)
// qDebug()<< prevEnv.at(i).toLocal8Bit().constData() << endl;
voidReply = iface.call(QStringLiteral("setEnvironment"), QStringList());
QVERIFY(voidReply.isValid());
listReply = iface.call(QStringLiteral("environment"));
QVERIFY(listReply.isValid());
QCOMPARE(listReply.value(), QStringList());
voidReply = iface.call(QStringLiteral("setEnvironment"), prevEnv);
QVERIFY(voidReply.isValid());
listReply = iface.call(QStringLiteral("environment"));
QVERIFY(listReply.isValid());
QCOMPARE(listReply.value(), prevEnv);
//****************** Test is/set title
// TODO: Consider checking what is in Profile
stringReply = iface.call(QStringLiteral("title"), Session::NameRole);
QVERIFY(stringReply.isValid());
// set title to, what title should be
QMap<QString, QString> titleMap;
titleMap[QStringLiteral("Shell")] = QStringLiteral("Shell");
// BUG: It appears that Session::LocalTabTitle is set to Shell and
// doesn't change. While RemoteTabTitle is actually the LocalTabTitle
// TODO: Figure out what's going on...
QMapIterator<QString, QString> i(titleMap);
while (i.hasNext()) {
i.next();
voidReply = iface.call(QStringLiteral("setTitle"), Session::NameRole, i.key());
QVERIFY(voidReply.isValid());
stringReply = iface.call(QStringLiteral("title"), Session::NameRole);
QVERIFY(stringReply.isValid());
QCOMPARE(stringReply.value(), i.value());
}
}
void DBusTest::testWindows()
{
// Tested functions:
// [+] int sessionCount();
// [+] QStringList sessionList();
// [+] int currentSession();
// [+] void setCurrentSession(int sessionId);
// [+] int newSession();
// [+] int newSession(const QString &profile);
// [+] int newSession(const QString &profile, const QString &directory);
// [ ] QString defaultProfile();
// [ ] QStringList profileList();
// [ ] void nextSession();
// [ ] void prevSession();
// [ ] void moveSessionLeft();
// [ ] void moveSessionRight();
// [ ] void setTabWidthToText(bool);
QDBusReply<int> intReply;
QDBusReply<QStringList> listReply;
QDBusReply<void> voidReply;
QDBusReply<QString> stringReply;
int sessionCount = -1;
int initialSessionId = -1;
QDBusInterface iface(_interfaceName, QStringLiteral("/Windows/1"), QStringLiteral("org.kde.konsole.Window"));
QVERIFY(iface.isValid());
intReply = iface.call(QStringLiteral("sessionCount"));
QVERIFY(intReply.isValid());
sessionCount = intReply.value();
QVERIFY(sessionCount > 0);
intReply = iface.call(QStringLiteral("currentSession"));
QVERIFY(intReply.isValid());
initialSessionId = intReply.value();
intReply = iface.call(QStringLiteral("newSession"));
QVERIFY(intReply.isValid());
++sessionCount;
int newSessionId = intReply.value();
QVERIFY(newSessionId != initialSessionId);
listReply = iface.call(QStringLiteral("sessionList"));
QVERIFY(listReply.isValid());
QStringList sessions = listReply.value();
QVERIFY(sessions.contains(QString::number(initialSessionId)));
QVERIFY(sessions.contains(QString::number(newSessionId)));
QVERIFY(sessions.size() == sessionCount);
intReply = iface.call(QStringLiteral("newSession"), _testProfileName);
QVERIFY(intReply.isValid());
++sessionCount;
newSessionId = intReply.value();
QVERIFY(newSessionId != initialSessionId);
{
QDBusInterface sessionIface(_interfaceName, QStringLiteral("/Sessions/%1").arg(newSessionId), QStringLiteral("org.kde.konsole.Session"));
QVERIFY(iface.isValid());
listReply = sessionIface.call(QStringLiteral("environment"));
QVERIFY(listReply.isValid());
QStringList env = listReply.value();
QVERIFY(env.contains(_testProfileEnv));
}
const auto tempDirectories = QStandardPaths::standardLocations(QStandardPaths::TempLocation);
const QString sessionDirectory = tempDirectories.empty() ? QStringLiteral("/") : tempDirectories.constFirst();
intReply = iface.call(QStringLiteral("newSession"), _testProfileName, sessionDirectory);
QVERIFY(intReply.isValid());
++sessionCount;
newSessionId = intReply.value();
QVERIFY(newSessionId != initialSessionId);
{
QDBusInterface sessionIface(_interfaceName, QStringLiteral("/Sessions/%1").arg(newSessionId), QStringLiteral("org.kde.konsole.Session"));
QVERIFY(iface.isValid());
listReply = sessionIface.call(QStringLiteral("environment"));
QVERIFY(listReply.isValid());
QStringList env = listReply.value();
QVERIFY(env.contains(_testProfileEnv));
// Apparently there's no function for checking CWD.
// The profile uses "%D" as title format, so check the title
stringReply = sessionIface.call(QStringLiteral("title"), Session::DisplayedTitleRole);
QVERIFY(stringReply.isValid());
QVERIFY(QDir(stringReply.value()) == QDir(sessionDirectory));
}
voidReply = iface.call(QStringLiteral("setCurrentSession"), initialSessionId);
QVERIFY(voidReply.isValid());
intReply = iface.call(QStringLiteral("currentSession"));
QVERIFY(intReply.isValid());
QVERIFY(intReply.value() == initialSessionId);
intReply = iface.call(QStringLiteral("sessionCount"));
QVERIFY(intReply.isValid());
QVERIFY(intReply.value() == sessionCount);
}
QTEST_MAIN(DBusTest)
#include "moc_DBusTest.cpp"
@@ -0,0 +1,46 @@
/*
SPDX-FileCopyrightText: 2010 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef DBUSTEST_H
#define DBUSTEST_H
#include <QDBusConnectionInterface>
#include <QDBusInterface>
#include <QDBusReply>
#include <QTest>
#include <QTextCodec>
#include <unistd.h>
class QProcess;
namespace Konsole
{
class DBusTest : public QObject
{
Q_OBJECT
public:
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testSessions();
void testWindows();
// protected slots are not treated as test cases
protected Q_SLOTS:
private:
QString _interfaceName;
QProcess *_process = nullptr;
QString _testProfileName;
QString _testProfilePath;
QString _testProfileEnv;
};
}
#endif // DBUSTEST_H
@@ -0,0 +1,223 @@
/*
SPDX-FileCopyrightText: 2013 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "HistoryTest.h"
#include <QTest>
// Konsole
#include "../Emulation.h"
#include "../session/Session.h"
using namespace Konsole;
void HistoryTest::initTestCase()
{
}
void HistoryTest::cleanupTestCase()
{
delete[] testImage;
}
void HistoryTest::testHistoryNone()
{
std::unique_ptr<HistoryType> historyTypeNone = std::make_unique<HistoryTypeNone>();
QCOMPARE(historyTypeNone->isEnabled(), false);
QCOMPARE(historyTypeNone->isUnlimited(), false);
QCOMPARE(historyTypeNone->maximumLineCount(), 0);
}
void HistoryTest::testHistoryFile()
{
std::unique_ptr<HistoryType> historyTypeFile = std::make_unique<HistoryTypeFile>();
QCOMPARE(historyTypeFile->isEnabled(), true);
QCOMPARE(historyTypeFile->isUnlimited(), true);
QCOMPARE(historyTypeFile->maximumLineCount(), -1);
}
void HistoryTest::testCompactHistory()
{
std::unique_ptr<HistoryType> historyTypeCompact = std::make_unique<CompactHistoryType>(42);
QCOMPARE(historyTypeCompact->isEnabled(), true);
QCOMPARE(historyTypeCompact->isUnlimited(), false);
QCOMPARE(historyTypeCompact->maximumLineCount(), 42);
}
void HistoryTest::testEmulationHistory()
{
auto session = std::make_unique<Session>();
Emulation *emulation = session->emulation();
const HistoryType &historyTypeDefault = emulation->history();
QCOMPARE(historyTypeDefault.isEnabled(), false);
QCOMPARE(historyTypeDefault.isUnlimited(), false);
QCOMPARE(historyTypeDefault.maximumLineCount(), 0);
emulation->setHistory(HistoryTypeNone());
const HistoryType &historyTypeNone = emulation->history();
QCOMPARE(historyTypeNone.isEnabled(), false);
QCOMPARE(historyTypeNone.isUnlimited(), false);
QCOMPARE(historyTypeNone.maximumLineCount(), 0);
emulation->setHistory(HistoryTypeFile());
const HistoryType &historyTypeFile = emulation->history();
QCOMPARE(historyTypeFile.isEnabled(), true);
QCOMPARE(historyTypeFile.isUnlimited(), true);
QCOMPARE(historyTypeFile.maximumLineCount(), -1);
emulation->setHistory(CompactHistoryType(42));
const HistoryType &compactHistoryType = emulation->history();
QCOMPARE(compactHistoryType.isEnabled(), true);
QCOMPARE(compactHistoryType.isUnlimited(), false);
QCOMPARE(compactHistoryType.maximumLineCount(), 42);
}
void HistoryTest::testHistoryScroll()
{
HistoryScroll *historyScroll;
// None
historyScroll = new HistoryScrollNone();
QVERIFY(!historyScroll->hasScroll());
QCOMPARE(historyScroll->getLines(), 0);
const HistoryType &historyTypeNone = historyScroll->getType();
QCOMPARE(historyTypeNone.isEnabled(), false);
QCOMPARE(historyTypeNone.isUnlimited(), false);
QCOMPARE(historyTypeNone.maximumLineCount(), 0);
delete historyScroll;
// File
historyScroll = new HistoryScrollFile();
QVERIFY(historyScroll->hasScroll());
QCOMPARE(historyScroll->getLines(), 0);
const HistoryType &historyTypeFile = historyScroll->getType();
QCOMPARE(historyTypeFile.isEnabled(), true);
QCOMPARE(historyTypeFile.isUnlimited(), true);
QCOMPARE(historyTypeFile.maximumLineCount(), -1);
delete historyScroll;
// Compact
historyScroll = new CompactHistoryScroll(42);
QVERIFY(historyScroll->hasScroll());
QCOMPARE(historyScroll->getLines(), 0);
const HistoryType &compactHistoryType = historyScroll->getType();
QCOMPARE(compactHistoryType.isEnabled(), true);
QCOMPARE(compactHistoryType.isUnlimited(), false);
QCOMPARE(compactHistoryType.maximumLineCount(), 42);
delete historyScroll;
}
void HistoryTest::testHistoryReflow()
{
testImage = new Character[testStringSize];
Character testChar;
for (int i = 0; i < testStringSize; i++) {
testImage[i] = Character((uint)testString[i]);
}
// None
auto historyScrollNone = std::unique_ptr<HistoryScrollNone>(new HistoryScrollNone());
QCOMPARE(historyScrollNone->getMaxLines(), 0);
QCOMPARE(historyScrollNone->reflowLines(10), 0);
// Compact
auto compactHistoryScroll = std::unique_ptr<CompactHistoryScroll>(new CompactHistoryScroll(10));
QCOMPARE(compactHistoryScroll->getMaxLines(), 10);
compactHistoryScroll->addCells(testImage, testStringSize);
compactHistoryScroll->addLine();
QCOMPARE(compactHistoryScroll->getLines(), 1);
QCOMPARE(compactHistoryScroll->reflowLines(10), 0);
QCOMPARE(compactHistoryScroll->getLines(), 4);
QCOMPARE(compactHistoryScroll->reflowLines(1), 26);
QCOMPARE(compactHistoryScroll->getLines(), 10);
QCOMPARE(compactHistoryScroll->getLineLen(5), 1);
compactHistoryScroll->getCells(3, 0, 1, &testChar);
QCOMPARE(testChar, testImage[testStringSize - 7]);
compactHistoryScroll->getCells(0, 0, 1, &testChar);
QCOMPARE(testChar, testImage[testStringSize - 10]);
compactHistoryScroll->getCells(9, 0, 1, &testChar);
QCOMPARE(testChar, testImage[testStringSize - 1]);
// File
auto historyScrollFile = std::unique_ptr<HistoryScrollFile>(new HistoryScrollFile());
QCOMPARE(historyScrollFile->getMaxLines(), 0);
historyScrollFile->addCells(testImage, testStringSize);
historyScrollFile->addLine();
QCOMPARE(historyScrollFile->getLines(), 1);
QCOMPARE(historyScrollFile->getMaxLines(), 1);
QCOMPARE(historyScrollFile->reflowLines(10), 0);
QCOMPARE(historyScrollFile->getLines(), 4);
QCOMPARE(historyScrollFile->getMaxLines(), 4);
QCOMPARE(historyScrollFile->reflowLines(1), 0);
QCOMPARE(historyScrollFile->getLines(), testStringSize);
QCOMPARE(historyScrollFile->getLineLen(5), 1);
historyScrollFile->getCells(3, 0, 1, &testChar);
QCOMPARE(testChar, testImage[3]);
historyScrollFile->getCells(0, 0, 1, &testChar);
QCOMPARE(testChar, testImage[0]);
historyScrollFile->getCells(testStringSize - 1, 0, 1, &testChar);
QCOMPARE(testChar, testImage[testStringSize - 1]);
}
void HistoryTest::testHistoryTypeChange()
{
std::unique_ptr<HistoryScroll> historyScroll(nullptr);
const char testString[] = "abcdefghijklmnopqrstuvwxyz1234567890";
const int testStringSize = sizeof(testString) / sizeof(char) - 1;
auto testImage = std::make_unique<Character[]>(testStringSize);
Character testChar;
for (int i = 0; i < testStringSize; i++) {
testImage[i] = Character((uint)testString[i]);
}
// None
auto historyTypeNone = std::make_unique<HistoryTypeNone>();
historyTypeNone->scroll(historyScroll);
// None to File
auto historyTypeFile = std::make_unique<HistoryTypeFile>();
historyTypeFile->scroll(historyScroll);
historyScroll->addCells(testImage.get(), testStringSize);
historyScroll->addLine();
QCOMPARE(historyScroll->reflowLines(1), 0);
QCOMPARE(historyScroll->getLines(), testStringSize);
historyScroll->getCells(0, 0, 1, &testChar);
QCOMPARE(testChar, testImage[0]);
// File to Compact
auto compactHistoryType = std::make_unique<CompactHistoryType>(10);
compactHistoryType->scroll(historyScroll);
QCOMPARE(historyScroll->getLines(), 10);
historyScroll->getCells(0, 0, 1, &testChar);
QCOMPARE(testChar, testImage[testStringSize - 10]);
// Compact to File
historyTypeFile->scroll(historyScroll);
QCOMPARE(historyScroll->getLines(), 10);
historyScroll->getCells(0, 0, 1, &testChar);
QCOMPARE(testChar, testImage[testStringSize - 10]);
// File to None
historyTypeNone->scroll(historyScroll);
QCOMPARE(historyScroll->getLines(), 0);
}
QTEST_MAIN(HistoryTest)
#include "moc_HistoryTest.cpp"
@@ -0,0 +1,45 @@
/*
SPDX-FileCopyrightText: 2013 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef HISTORYTEST_H
#define HISTORYTEST_H
#include <kde_terminal_interface.h>
#include "../characters/Character.h"
#include "../history/HistoryScrollFile.h"
#include "../history/HistoryScrollNone.h"
#include "../history/HistoryTypeFile.h"
#include "../history/HistoryTypeNone.h"
#include "../history/compact/CompactHistoryScroll.h"
#include "../history/compact/CompactHistoryType.h"
namespace Konsole
{
class HistoryTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testHistoryNone();
void testHistoryFile();
void testCompactHistory();
void testEmulationHistory();
void testHistoryScroll();
void testHistoryReflow();
void testHistoryTypeChange();
private:
static constexpr const char testString[] = "abcdefghijklmnopqrstuvwxyz1234567890";
static constexpr const int testStringSize = sizeof(testString) / sizeof(char) - 1;
Character *testImage = nullptr;
};
}
#endif // HISTORYTEST_H
@@ -0,0 +1,125 @@
/*
SPDX-FileCopyrightText: 2022 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "HotSpotFilterTest.h"
#include <QTest>
QTEST_GUILESS_MAIN(HotSpotFilterTest)
void HotSpotFilterTest::testUrlFilterRegex_data()
{
QTest::addColumn<QString>("url");
QTest::addColumn<QString>("expectedUrl");
QTest::addColumn<bool>("matchResult");
// A space, \n, or \t before the url to match what happens at runtime,
// i.e. to match "http" but not "foohttp"
QTest::newRow("url_simple") << " https://api.kde.org"
<< "https://api.kde.org" << true;
QTest::newRow("url_with_port") << "\nhttps://api.kde.org:2098"
<< "https://api.kde.org:2098" << true;
QTest::newRow("url_with_port_trailing_slash") << "\nhttps://api.kde.org:2098/"
<< "https://api.kde.org:2098/" << true;
QTest::newRow("url_with_numeric_host") << "\nhttp://127.0.0.1"
<< "http://127.0.0.1" << true;
QTest::newRow("url_with_numeric_host_port") << "\nhttp://127.0.0.1:4000"
<< "http://127.0.0.1:4000" << true;
QTest::newRow("url_with_numeric_host_port_slash") << "\nhttp://127.0.0.1:4000/"
<< "http://127.0.0.1:4000/" << true;
QTest::newRow("url_with_path") << "https://api.kde.org/path/to/somewhere"
<< "https://api.kde.org/path/to/somewhere" << true;
QTest::newRow("url_with_query") << "https://user:pass@api.kde.org?somequery=foo"
<< "https://user:pass@api.kde.org?somequery=foo" << true;
QTest::newRow("url_with_port_path") << " https://api.kde.org:2098/path/to/somewhere"
<< "https://api.kde.org:2098/path/to/somewhere" << true;
QTest::newRow("url_with_user_password") << "\thttps://user:blah@api.kde.org"
<< "https://user:blah@api.kde.org" << true;
QTest::newRow("url_with_user_password_port_fragment") << " https://user:blah@api.kde.org:2098#fragment"
<< "https://user:blah@api.kde.org:2098#fragment" << true;
QTest::newRow("url_all_bells") << " https://user:pass@api.kde.org:2098/path/to/somewhere?somequery=foo#fragment"
<< "https://user:pass@api.kde.org:2098/path/to/somewhere?somequery=foo#fragment" << true;
QTest::newRow("uppercase") << " https://invent.kde.org/frameworks/ktexteditor/-/blob/master/README.md"
<< "https://invent.kde.org/frameworks/ktexteditor/-/blob/master/README.md" << true;
QTest::newRow("markup") << " [https://foobar](https://foobar)"
<< "https://foobar" << true;
QTest::newRow("markup_parens") << "[unix-history-repo](https://github.com/dspinellis/unix-history-repo)"
<< "https://github.com/dspinellis/unix-history-repo" << true;
QTest::newRow("markup_with_parens_inside_parens") << "[*Das verrückte Labyrinth*](https://en.wikipedia.org/wiki/Labyrinth_(board_game))"
<< "https://en.wikipedia.org/wiki/Labyrinth_(board_game)" << true;
QTest::newRow("bracket_before") << "[198]http://www.ietf.org/rfc/rfc2396.txt"
<< "http://www.ietf.org/rfc/rfc2396.txt" << true;
QTest::newRow("quote_before") << "\"http://www.ietf.org/rfc/rfc2396.txt"
<< "http://www.ietf.org/rfc/rfc2396.txt" << true;
QTest::newRow("grave_before") << "`https://foo.bar`"
<< "https://foo.bar" << true;
QTest::newRow("equals_before") << "foo=https://foo.bar"
<< "https://foo.bar" << true;
QTest::newRow("url_inside_angle_brackets") << "<https://google.com>"
<< "https://google.com" << true;
QTest::newRow("file_scheme") << "file:///some/file"
<< "file:///some/file" << true;
QTest::newRow("uppercase_host") << "https://EXAMPLE.com"
<< "https://EXAMPLE.com" << true;
QTest::newRow("uppercase_query") << "https://example.com?fooOpt=barVal"
<< "https://example.com?fooOpt=barVal" << true;
QTest::newRow("uppercase_fragment") << "https://example.com?fooOpt=barVal#FRAG"
<< "https://example.com?fooOpt=barVal#FRAG" << true;
QTest::newRow("www") << " www.kde.org"
<< "www.kde.org" << true;
QTest::newRow("with_comma_in_path") << "https://example.com/foo,bar"
<< "https://example.com/foo,bar" << true;
QTest::newRow("empty_query") << "http://example.com/?"
<< "http://example.com/?" << true;
QTest::newRow("empty_fragment") << "http://example.com/#"
<< "http://example.com/#" << true;
QTest::newRow("www_followed_by_colon") << "www.example.com:foo@bar.com"
<< "www.example.com" << true;
QTest::newRow("ipv6") << "http://[2a00:1450:4001:829::200e]/"
<< "http://[2a00:1450:4001:829::200e]/" << true;
QTest::newRow("ipv6_with_port") << "http://[2a00:1450:4001:829::200e]:80/"
<< "http://[2a00:1450:4001:829::200e]:80/" << true;
QTest::newRow("query_with_question_marks") << "ldap://[2001:db8::7]/c=GB?objectClass?one"
<< "ldap://[2001:db8::7]/c=GB?objectClass?one" << true;
QTest::newRow("path_with_parens") << "https://en.wikipedia.org/wiki/C_(programming_language)"
<< "https://en.wikipedia.org/wiki/C_(programming_language)" << true;
QTest::newRow("query_with_parens") << "http://en.wikipedia.org/w/index.php?title=Thresholding_(image_processing)&oldid=132306976"
<< "http://en.wikipedia.org/w/index.php?title=Thresholding_(image_processing)&oldid=132306976" << true;
QTest::newRow("fragment_with_parens") << "https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences"
<< "https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences" << true;
QTest::newRow("url_with_lots_of_parens") << "(https://example.com/foo(bar(baz(qux)quux)quuux))))"
<< "https://example.com/foo(bar(baz(qux)quux)quuux)" << true;
}
void HotSpotFilterTest::testUrlFilterRegex()
{
QFETCH(QString, url);
QFETCH(QString, expectedUrl);
QFETCH(bool, matchResult);
const QRegularExpression &regex = Konsole::UrlFilter::FullUrlRegExp;
const QRegularExpressionMatch match = regex.match(url);
// qDebug() << match;
QCOMPARE(match.hasMatch(), matchResult);
if (matchResult) {
QCOMPARE(match.capturedView(0), expectedUrl);
}
}
#include "moc_HotSpotFilterTest.cpp"
@@ -0,0 +1,21 @@
/*
SPDX-FileCopyrightText: 2022 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef HOTSPOTFILTERTEST_H
#define HOTSPOTFILTERTEST_H
#include "filterHotSpots/UrlFilter.h"
class HotSpotFilterTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testUrlFilterRegex_data();
void testUrlFilterRegex();
};
#endif // HOTSPOTFILTERTEST_H
@@ -0,0 +1,187 @@
/*
SPDX-FileCopyrightText: 2013, 2018 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "KeyboardTranslatorTest.h"
#include "keyboardtranslator/FallbackKeyboardTranslator.h"
#include "keyboardtranslator/KeyboardTranslatorReader.h"
// KDE
#include <QTest>
using namespace Konsole;
Q_DECLARE_METATYPE(Qt::KeyboardModifiers)
void KeyboardTranslatorTest::testEntryTextWildcards_data()
{
// Shift = 1 + (1 << 0) = 2
// Alt = 1 + (1 << 2) = 3
// Control = 1 + (1 << 4) = 5
QTest::addColumn<QByteArray>("text");
QTest::addColumn<QByteArray>("result");
QTest::addColumn<bool>("wildcards");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::newRow("Home no wildcards no modifiers") << QByteArray("Home") << QByteArray("Home") << false << Qt::KeyboardModifiers(Qt::NoModifier);
QTest::newRow("Home no wildcards Shift modifiers") << QByteArray("Home") << QByteArray("Home") << false << Qt::KeyboardModifiers(Qt::ShiftModifier);
QTest::newRow("Home no wildcards Alt modifiers") << QByteArray("Home") << QByteArray("Home") << false << Qt::KeyboardModifiers(Qt::AltModifier);
QTest::newRow("Home no wildcards Control modifiers") << QByteArray("Home") << QByteArray("Home") << false << Qt::KeyboardModifiers(Qt::ControlModifier);
QTest::newRow("Home yes wildcards no modifiers") << QByteArray("Home") << QByteArray("Home") << true << Qt::KeyboardModifiers(Qt::NoModifier);
QTest::newRow("Home yes wildcards Shift modifiers") << QByteArray("Home") << QByteArray("Home") << true << Qt::KeyboardModifiers(Qt::ShiftModifier);
QTest::newRow("Home yes wildcards Alt modifiers") << QByteArray("Home") << QByteArray("Home") << true << Qt::KeyboardModifiers(Qt::AltModifier);
QTest::newRow("Home yes wildcards Control modifiers") << QByteArray("Home") << QByteArray("Home") << true << Qt::KeyboardModifiers(Qt::ControlModifier);
// text, results: no mod, shift, alt, control
QList<QByteArray> entry;
entry << QByteArray("E*") << QByteArray("E1") << QByteArray("E2") << QByteArray("E3") << QByteArray("E5");
QTest::newRow("E* yes wildcards no modifiers") << entry[0] << entry[1] << true << Qt::KeyboardModifiers(Qt::NoModifier);
QTest::newRow("E* yes wildcards Shift modifiers") << entry[0] << entry[2] << true << Qt::KeyboardModifiers(Qt::ShiftModifier);
QTest::newRow("E* yes wildcards Alt modifiers") << entry[0] << entry[3] << true << Qt::KeyboardModifiers(Qt::AltModifier);
QTest::newRow("E* yes wildcards Control modifiers") << entry[0] << entry[4] << true << Qt::KeyboardModifiers(Qt::ControlModifier);
// combinations
entry.clear();
entry << QByteArray("E*") << QByteArray("E4") << QByteArray("E6") << QByteArray("E8") << QByteArray("E7");
QTest::newRow("E* yes wildcards Shift+Alt modifiers") << entry[0] << entry[1] << true << Qt::KeyboardModifiers(Qt::ShiftModifier | Qt::AltModifier);
QTest::newRow("E* yes wildcards Shift+Control modifiers") << entry[0] << entry[2] << true << Qt::KeyboardModifiers(Qt::ShiftModifier | Qt::ControlModifier);
QTest::newRow("E* yes wildcards Shift+Alt+Control modifiers")
<< entry[0] << entry[3] << true << Qt::KeyboardModifiers(Qt::ShiftModifier | Qt::AltModifier | Qt::ControlModifier);
QTest::newRow("E* yes wildcards Alt+Control modifiers") << entry[0] << entry[4] << true << Qt::KeyboardModifiers(Qt::AltModifier | Qt::ControlModifier);
// text, results: no mod, shift, alt, control
entry.clear();
entry << QByteArray("\033[24;*~") << QByteArray("\033[24;1~") << QByteArray("\033[24;2~") << QByteArray("\033[24;3~") << QByteArray("\033[24;5~");
QTest::newRow("\033[24;*~ yes wildcards no modifiers") << entry[0] << entry[1] << true << Qt::KeyboardModifiers(Qt::NoModifier);
QTest::newRow("\033[24;*~ yes wildcards Shift modifiers") << entry[0] << entry[2] << true << Qt::KeyboardModifiers(Qt::ShiftModifier);
QTest::newRow("\033[24;*~ yes wildcards Alt modifiers") << entry[0] << entry[3] << true << Qt::KeyboardModifiers(Qt::AltModifier);
QTest::newRow("\033[24;*~ yes wildcards Control modifiers") << entry[0] << entry[4] << true << Qt::KeyboardModifiers(Qt::ControlModifier);
// combinations
entry.clear();
entry << QByteArray("\033[24;*~") << QByteArray("\033[24;4~") << QByteArray("\033[24;6~") << QByteArray("\033[24;8~") << QByteArray("\033[24;7~");
QTest::newRow("\033[24;*~ yes wildcards Shift+Alt modifiers") << entry[0] << entry[1] << true << Qt::KeyboardModifiers(Qt::ShiftModifier | Qt::AltModifier);
QTest::newRow("\033[24;*~ yes wildcards Shift+Control modifiers")
<< entry[0] << entry[2] << true << Qt::KeyboardModifiers(Qt::ShiftModifier | Qt::ControlModifier);
QTest::newRow("\033[24;*~ yes wildcards Shift+Alt+Control modifiers")
<< entry[0] << entry[3] << true << Qt::KeyboardModifiers(Qt::ShiftModifier | Qt::AltModifier | Qt::ControlModifier);
QTest::newRow("\033[24;*~ yes wildcards Alt+Control modifiers")
<< entry[0] << entry[4] << true << Qt::KeyboardModifiers(Qt::AltModifier | Qt::ControlModifier);
}
void KeyboardTranslatorTest::testEntryTextWildcards()
{
QFETCH(QByteArray, text);
QFETCH(QByteArray, result);
QFETCH(bool, wildcards);
QFETCH(Qt::KeyboardModifiers, modifiers);
KeyboardTranslator::Entry entry;
entry.setText(text);
QCOMPARE(entry.text(wildcards, modifiers), result);
}
// Use FallbackKeyboardTranslator to test basic functionality
void KeyboardTranslatorTest::testFallback()
{
auto fallback = std::unique_ptr<FallbackKeyboardTranslator>(new FallbackKeyboardTranslator());
QCOMPARE(QStringLiteral("fallback"), fallback->name());
QCOMPARE(QStringLiteral("Fallback Keyboard Translator"), fallback->description());
auto entries = fallback->entries();
QCOMPARE(1, entries.size());
auto entry = fallback->findEntry(Qt::Key_Tab, Qt::NoModifier);
QVERIFY(!entry.isNull());
QCOMPARE(FallbackKeyboardTranslator::Command::NoCommand, entry.command());
QCOMPARE(int(Qt::Key_Tab), entry.keyCode());
QCOMPARE(QByteArray("\t"), entry.text());
QCOMPARE(QByteArray("\\t"), entry.escapedText());
QCOMPARE(Qt::KeyboardModifiers(Qt::NoModifier), entry.modifiers());
QCOMPARE(Qt::KeyboardModifiers(Qt::NoModifier), entry.modifierMask());
QCOMPARE(KeyboardTranslator::States(KeyboardTranslator::NoState), entry.state());
QCOMPARE(QStringLiteral("Tab"), entry.conditionToString());
QCOMPARE(QStringLiteral("\\t"), entry.resultToString());
QVERIFY(entry.matches(Qt::Key_Tab, Qt::NoModifier, KeyboardTranslator::NoState));
QVERIFY(entry == fallback->findEntry(Qt::Key_Tab, Qt::NoModifier));
}
void KeyboardTranslatorTest::testHexKeys()
{
QFile linuxkeytab(QFINDTESTDATA(QStringLiteral("data/test.keytab")));
QVERIFY(linuxkeytab.exists());
QVERIFY(linuxkeytab.open(QIODevice::ReadOnly));
auto translator = std::unique_ptr<KeyboardTranslator>(new KeyboardTranslator(QStringLiteral("testtranslator")));
KeyboardTranslatorReader reader(&linuxkeytab);
while (reader.hasNextEntry()) {
translator->addEntry(reader.nextEntry());
}
linuxkeytab.close();
// A worthless check ATM
if (reader.parseError()) {
QFAIL("Parse failure");
}
QCOMPARE(QStringLiteral("testtranslator"), translator->name());
QCOMPARE(QString(), translator->description());
auto entry = translator->findEntry(Qt::Key_Backspace, Qt::NoModifier);
QVERIFY(!entry.isNull());
QCOMPARE(FallbackKeyboardTranslator::Command::NoCommand, entry.command());
QCOMPARE(int(Qt::Key_Backspace), entry.keyCode());
QCOMPARE(QByteArray("\x7F"), entry.text());
QCOMPARE(QByteArray("\\x7f"), entry.escapedText());
QCOMPARE(Qt::KeyboardModifiers(Qt::NoModifier), entry.modifiers());
QCOMPARE(Qt::KeyboardModifiers(Qt::NoModifier), entry.modifierMask());
QCOMPARE(KeyboardTranslator::States(KeyboardTranslator::NoState), entry.state());
QCOMPARE(QStringLiteral("Backspace"), entry.conditionToString());
QCOMPARE(QStringLiteral("\\x7f"), entry.resultToString());
QVERIFY(entry.matches(Qt::Key_Backspace, Qt::NoModifier, KeyboardTranslator::NoState));
QVERIFY(entry == translator->findEntry(Qt::Key_Backspace, Qt::NoModifier));
entry = translator->findEntry(Qt::Key_Delete, Qt::NoModifier);
QVERIFY(!entry.isNull());
QCOMPARE(FallbackKeyboardTranslator::Command::NoCommand, entry.command());
QCOMPARE(int(Qt::Key_Delete), entry.keyCode());
QCOMPARE(QByteArray("\x08"), entry.text());
QCOMPARE(QByteArray("\\b"), entry.escapedText());
QCOMPARE(Qt::KeyboardModifiers(Qt::NoModifier), entry.modifiers());
QCOMPARE(Qt::KeyboardModifiers(Qt::NoModifier), entry.modifierMask());
QCOMPARE(KeyboardTranslator::States(KeyboardTranslator::NoState), entry.state());
QCOMPARE(QStringLiteral("Del"), entry.conditionToString());
QCOMPARE(QStringLiteral("\\b"), entry.resultToString());
QVERIFY(entry.matches(Qt::Key_Delete, Qt::NoModifier, KeyboardTranslator::NoState));
QVERIFY(!entry.matches(Qt::Key_Backspace, Qt::NoModifier, KeyboardTranslator::NoState));
QVERIFY(!(entry == translator->findEntry(Qt::Key_Backspace, Qt::NoModifier)));
entry = translator->findEntry(Qt::Key_Space, Qt::NoModifier);
QVERIFY(!entry.isNull());
QCOMPARE(FallbackKeyboardTranslator::Command::NoCommand, entry.command());
QCOMPARE(int(Qt::Key_Space), entry.keyCode());
QEXPECT_FAIL("", "Several keytabs use x00 as Space +Control; text() fails", Continue);
QCOMPARE(QByteArray("\x00"), entry.text());
QCOMPARE(QByteArray("\\x00"), entry.escapedText());
QCOMPARE(Qt::KeyboardModifiers(Qt::NoModifier), entry.modifiers());
QCOMPARE(Qt::KeyboardModifiers(Qt::NoModifier), entry.modifierMask());
QCOMPARE(KeyboardTranslator::States(KeyboardTranslator::NoState), entry.state());
QCOMPARE(QStringLiteral("Space"), entry.conditionToString());
QCOMPARE(QStringLiteral("\\x00"), entry.resultToString());
QVERIFY(entry.matches(Qt::Key_Space, Qt::NoModifier, KeyboardTranslator::NoState));
QVERIFY(entry == translator->findEntry(Qt::Key_Space, Qt::NoModifier));
QVERIFY(!entry.matches(Qt::Key_Backspace, Qt::NoModifier, KeyboardTranslator::NoState));
QVERIFY(!(entry == translator->findEntry(Qt::Key_Backspace, Qt::NoModifier)));
}
QTEST_GUILESS_MAIN(KeyboardTranslatorTest)
#include "moc_KeyboardTranslatorTest.cpp"
@@ -0,0 +1,28 @@
/*
SPDX-FileCopyrightText: 2013 Kurt Hindenburg <kurt.hindenburg@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KEYBOARDTRANSLATORTEST_H
#define KEYBOARDTRANSLATORTEST_H
#include "keyboardtranslator/KeyboardTranslator.h"
#include <QObject>
namespace Konsole
{
class KeyboardTranslatorTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testEntryTextWildcards();
void testEntryTextWildcards_data();
void testFallback();
void testHexKeys();
};
}
#endif // KEYBOARDTRANSLATORTEST_H
@@ -0,0 +1,140 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "PartTest.h"
// Qt
#include <QDialog>
#include <QFileInfo>
#include <QLabel>
#include <QTimer>
#include <QVBoxLayout>
// KDE
#include <KPluginFactory>
#include <KPtyDevice>
#include <KPtyProcess>
#include <QTest>
// Konsole
#include "../Pty.h"
using namespace Konsole;
void PartTest::initTestCase()
{
/* Try to test against build konsolepart, so move directory containing
executable to front of libraryPaths. KPluginLoader should find the
part first in the build dir over the system installed ones.
I believe the CI installs first and then runs the test so the other
paths can not be removed.
*/
const auto libraryPaths = QCoreApplication::libraryPaths();
auto buildPath = libraryPaths.last();
QCoreApplication::removeLibraryPath(buildPath);
// konsolepart.so is in ../autotests/
if (buildPath.endsWith(QStringLiteral("/autotests"))) {
buildPath.chop(10);
}
QCoreApplication::addLibraryPath(buildPath);
}
void PartTest::testFdShell()
{
#if defined(Q_OS_FREEBSD)
QSKIP("Skipping on CI FreeBSD14_qt6x", SkipSingle);
return;
#endif
testFd(true);
}
void PartTest::testFdStandalone()
{
testFd(false);
}
void PartTest::testFd(bool runShell)
{
// find ping
QStringList pingList;
QFileInfo info;
QString pingExe;
pingList << QStringLiteral("/bin/ping") << QStringLiteral("/sbin/ping");
for (int i = 0; i < pingList.size(); ++i) {
info.setFile(pingList.at(i));
if (info.exists() && info.isExecutable()) {
pingExe = pingList.at(i);
}
}
if (pingExe.isEmpty()) {
QSKIP("ping command not found.");
return;
}
// create a Konsole part and attempt to connect to it
KParts::Part *terminalPart = createPart();
if (terminalPart == nullptr) { // not found
QFAIL("konsolepart not found.");
return;
}
// start a pty process
KPtyProcess ptyProcess;
ptyProcess.setProgram(pingExe, QStringList() << QStringLiteral("localhost"));
ptyProcess.setPtyChannels(KPtyProcess::AllChannels);
ptyProcess.start();
QVERIFY(ptyProcess.waitForStarted());
int fd = ptyProcess.pty()->masterFd();
// test that the 2nd argument of openTeletype is optional,
// to run without shell
if (runShell) {
// connect to an existing pty
bool result = QMetaObject::invokeMethod(terminalPart, "openTeletype", Qt::DirectConnection, Q_ARG(int, fd));
QVERIFY(result);
} else {
// test the optional 2nd argument of openTeletype, to run without shell
bool result = QMetaObject::invokeMethod(terminalPart, "openTeletype", Qt::DirectConnection, Q_ARG(int, fd), Q_ARG(bool, false));
QVERIFY(result);
}
// suspend the KPtyDevice so that the embedded terminal gets a chance to
// read from the pty. Otherwise the KPtyDevice will simply read everything
// as soon as it becomes available and the terminal will not display any output
ptyProcess.pty()->setSuspended(true);
QPointer<QDialog> dialog = new QDialog();
auto layout = new QVBoxLayout(dialog.data());
auto explanation = runShell ? QStringLiteral("Output of 'ping localhost' should appear in a terminal below for 5 seconds")
: QStringLiteral("Output of 'ping localhost' should appear standalone below for 5 seconds");
layout->addWidget(new QLabel(explanation));
layout->addWidget(terminalPart->widget());
QTimer::singleShot(5000, dialog.data(), &QDialog::close);
dialog.data()->exec();
delete terminalPart;
delete dialog.data();
ptyProcess.kill();
ptyProcess.waitForFinished(1000);
}
KParts::Part *PartTest::createPart()
{
const KPluginMetaData metaData(QStringLiteral("konsolepart"));
Q_ASSERT(metaData.isValid());
KPluginFactory::Result<KParts::Part> result = KPluginFactory::instantiatePlugin<KParts::Part>(metaData, this);
Q_ASSERT(result);
return result.plugin;
}
QTEST_MAIN(PartTest)
#include "moc_PartTest.cpp"
@@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef PARTTEST_H
#define PARTTEST_H
#include <KParts/Part>
#include <kde_terminal_interface.h>
namespace Konsole
{
class PartTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testFdShell();
void testFdStandalone();
private:
void testFd(bool runShell);
KParts::Part *createPart();
};
}
#endif // PARTTEST_H
@@ -0,0 +1,117 @@
/*
SPDX-FileCopyrightText: 2023 Theodore Wang <theodorewang12@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "ProcessInfoTest.h"
// Qt
#include <QDir>
#include <QString>
#include <QTest>
// Konsole
#include "../ProcessInfo.h"
#include "../session/Session.h"
// Others
#include <memory>
#include <QTest>
using namespace Konsole;
std::unique_ptr<ProcessInfo> ProcessInfoTest::createProcInfo(const KProcess &proc)
{
return std::unique_ptr<ProcessInfo>(ProcessInfo::newInstance(proc.processId()));
}
void ProcessInfoTest::testProcessValidity()
{
if (Session::checkProgram(QStringLiteral("bash")).isEmpty())
return;
KProcess proc;
proc.setProgram(QStringLiteral("bash"));
proc.start();
QVERIFY(createProcInfo(proc)->isValid());
proc.close();
proc.waitForFinished(1000);
}
void ProcessInfoTest::testProcessCwd()
{
#ifndef Q_OS_FREEBSD
if (Session::checkProgram(QStringLiteral("bash")).isEmpty())
return;
KProcess proc;
proc.setProgram({QStringLiteral("bash"), QStringLiteral("-x")});
proc.start();
auto procInfo = createProcInfo(proc);
const QString startDir(QDir::currentPath());
const QString parentDir(startDir.mid(0, startDir.lastIndexOf(QLatin1Char('/'))));
bool ok;
QString currDir;
currDir = procInfo->currentDir(&ok);
QVERIFY(ok);
QCOMPARE(currDir, startDir);
proc.write(QStringLiteral("cd ..\n").toLocal8Bit());
proc.waitForReadyRead(1000);
procInfo->update();
currDir = procInfo->currentDir(&ok);
QVERIFY(ok);
QCOMPARE(currDir, parentDir);
proc.write(QStringLiteral("exit\n").toLocal8Bit());
proc.waitForFinished(1000);
#endif
}
void ProcessInfoTest::testProcessNameSpecialChars()
{
#ifndef Q_OS_FREEBSD
if (Session::checkProgram(QStringLiteral("bash")).isEmpty())
return;
const QVector<QString> specNames({QStringLiteral("(( a("), QStringLiteral("("), QStringLiteral("ab) ("), QStringLiteral(")")});
KProcess mainProc;
mainProc.setProgram({QStringLiteral("bash"), QStringLiteral("-x")});
mainProc.start();
auto mainProcInfo = createProcInfo(mainProc);
bool ok;
for (auto specName : specNames) {
mainProc.write(QStringLiteral("cp $(which bash) '%1'\n").arg(specName).toLocal8Bit());
mainProc.waitForReadyRead(1000);
mainProc.write(QStringLiteral("exec %1'%2'\n").arg(QDir::currentPath() + QDir::separator(), specName).toLocal8Bit());
mainProc.waitForReadyRead(1000);
mainProcInfo->update();
QDir::current().remove(specName);
const QString currName(mainProcInfo->name(&ok));
QVERIFY(ok);
QCOMPARE(currName, specName);
}
mainProc.write(QStringLiteral("exit\n").toLocal8Bit());
mainProc.waitForFinished(1000);
#endif
}
QTEST_MAIN(ProcessInfoTest)
#include "moc_ProcessInfoTest.cpp"
@@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2023 Theodore Wang <theodorewang12@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef PROCESSINFOTEST_H
#define PROCESSINFOTEST_H
// Others
#include <KProcess>
#include <QObject>
namespace Konsole
{
class ProcessInfo;
class ProcessInfoTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
// testing of ProcessInfo::update is itnegrated into the tests
void testProcessValidity();
void testProcessCwd();
void testProcessNameSpecialChars();
private:
std::unique_ptr<ProcessInfo> createProcInfo(const KProcess &proc);
};
} // PROCESSINFOTEST_H
#endif
@@ -0,0 +1,320 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "ProfileTest.h"
// Qt
#include <QFile>
#include <QFileInfo>
#include <QStandardPaths>
#include <QTemporaryFile>
#include <QTest>
#include <QTextStream>
// KDE
// Konsole
#include "../profile/Profile.h"
#include "../profile/ProfileGroup.h"
#include "../profile/ProfileManager.cpp"
#include "../profile/ProfileReader.h"
#include "../profile/ProfileWriter.h"
#include <QStandardPaths>
#include <array>
using namespace Konsole;
void ProfileTest::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
}
void ProfileTest::testProfile()
{
// create a new profile
Profile *parent = new Profile;
parent->setProperty(Profile::Name, QStringLiteral("Parent"));
parent->setProperty(Profile::Path, QStringLiteral("FakePath"));
parent->setProperty(Profile::AntiAliasFonts, false);
parent->setProperty(Profile::StartInCurrentSessionDir, false);
parent->setProperty(Profile::UseCustomCursorColor, true);
QVERIFY(parent->useCustomCursorColor());
QCOMPARE(parent->customCursorColor(), QColor());
QCOMPARE(parent->customCursorTextColor(), QColor());
parent->setProperty(Profile::UseCustomCursorColor, false);
QVERIFY(!parent->useCustomCursorColor());
QCOMPARE(parent->customCursorColor(), QColor());
QCOMPARE(parent->customCursorTextColor(), QColor());
// create a child profile
Profile *child = new Profile(Profile::Ptr(parent));
child->setProperty(Profile::StartInCurrentSessionDir, true);
// check which properties are set
QVERIFY(parent->isPropertySet(Profile::Name));
QVERIFY(parent->isPropertySet(Profile::Path));
QVERIFY(parent->isPropertySet(Profile::AntiAliasFonts));
QVERIFY(!parent->isPropertySet(Profile::Icon));
QVERIFY(!parent->isPropertySet(Profile::Command));
QVERIFY(!parent->isPropertySet(Profile::Arguments));
QVERIFY(child->isPropertySet(Profile::StartInCurrentSessionDir));
QVERIFY(!child->isPropertySet(Profile::Name));
QVERIFY(!child->isPropertySet(Profile::AntiAliasFonts));
QVERIFY(!child->isPropertySet(Profile::ColorScheme));
// read non-inheritable properties
QCOMPARE(parent->property<QString>(Profile::Name), QStringLiteral("Parent"));
QCOMPARE(child->property<QVariant>(Profile::Name), QVariant());
QCOMPARE(parent->property<QString>(Profile::Path), QStringLiteral("FakePath"));
QCOMPARE(child->property<QVariant>(Profile::Path), QVariant());
// read inheritable properties
QVERIFY(!parent->property<bool>(Profile::AntiAliasFonts));
QVERIFY(!child->property<bool>(Profile::AntiAliasFonts));
QVERIFY(!parent->startInCurrentSessionDir());
QVERIFY(child->startInCurrentSessionDir());
delete child;
}
void ProfileTest::testClone()
{
// create source profile and parent
Profile::Ptr parent(new Profile);
parent->setProperty(Profile::Command, QStringLiteral("ps"));
parent->setProperty(Profile::ColorScheme, QStringLiteral("BlackOnWhite"));
Profile::Ptr source(new Profile(parent));
source->setProperty(Profile::AntiAliasFonts, false);
source->setProperty(Profile::HistorySize, 4567);
source->setProperty(Profile::Name, QStringLiteral("SourceProfile"));
source->setProperty(Profile::Path, QStringLiteral("SourcePath"));
// create target to clone source and parent
Profile::Ptr targetParent(new Profile);
// same value as source parent
targetParent->setProperty(Profile::Command, QStringLiteral("ps"));
// different value from source parent
targetParent->setProperty(Profile::ColorScheme, QStringLiteral("BlackOnGrey"));
Profile::Ptr target(new Profile(parent));
// clone source profile, setting only properties that differ
// between the source and target
target->clone(source, true);
// check that properties from source have been cloned into target
QCOMPARE(source->property<bool>(Profile::AntiAliasFonts), target->property<bool>(Profile::AntiAliasFonts));
QCOMPARE(source->property<int>(Profile::HistorySize), target->property<int>(Profile::HistorySize));
// check that Name and Path properties are handled specially and not cloned
QVERIFY(source->property<QString>(Profile::Name) != target->property<QString>(Profile::Name));
QVERIFY(source->property<QString>(Profile::Path) != target->property<QString>(Profile::Path));
// check that Command property is not set in target because the values
// are the same
QVERIFY(!target->isPropertySet(Profile::Command));
// check that ColorScheme property is cloned because the inherited values
// from the source parent and target parent differ
QCOMPARE(source->property<QString>(Profile::ColorScheme), target->property<QString>(Profile::ColorScheme));
}
void ProfileTest::testProfileGroup()
{
// create three new profiles
std::array<Profile::Ptr, 3> profile;
for (auto &i : profile) {
i = new Profile;
QVERIFY(!i->asGroup());
}
// set properties with different values
profile[0]->setProperty(Profile::UseCustomCursorColor, true);
profile[1]->setProperty(Profile::UseCustomCursorColor, false);
// set properties with same values
for (auto &i : profile) {
i->setProperty(Profile::HistorySize, 1234);
}
// create a group profile
ProfileGroup::Ptr group = ProfileGroup::Ptr(new ProfileGroup);
const ProfileGroup::Ptr group_const = ProfileGroup::Ptr(new ProfileGroup);
QVERIFY(group->asGroup());
QVERIFY(group_const->asGroup());
for (auto &i : profile) {
group->addProfile(i);
QVERIFY(group->profiles().contains(i));
QVERIFY(!group_const->profiles().contains(i));
}
group->updateValues();
// read and check properties from the group
QCOMPARE(group->property<int>(Profile::HistorySize), 1234);
QCOMPARE(group_const->property<int>(Profile::HistorySize), 0);
QCOMPARE(group->property<QVariant>(Profile::UseCustomCursorColor), QVariant());
QCOMPARE(group_const->property<QVariant>(Profile::UseCustomCursorColor), QVariant());
// set and test shareable properties in the group
group->setProperty(Profile::Command, QStringLiteral("ssh"));
group->setProperty(Profile::AntiAliasFonts, false);
QCOMPARE(profile[0]->property<QString>(Profile::Command), QStringLiteral("ssh"));
QVERIFY(!profile[1]->property<bool>(Profile::AntiAliasFonts));
// set and test non-shareable properties in the group
// (should have no effect)
group->setProperty(Profile::Name, QStringLiteral("NewName"));
group->setProperty(Profile::Path, QStringLiteral("NewPath"));
QVERIFY(profile[1]->property<QString>(Profile::Name) != QLatin1String("NewName"));
QVERIFY(profile[2]->property<QString>(Profile::Path) != QLatin1String("NewPath"));
// remove a profile from the group
group->removeProfile(profile[0]);
QVERIFY(!group->profiles().contains(profile[0]));
group->updateValues();
// check that profile is no longer affected by group
group->setProperty(Profile::Command, QStringLiteral("fish"));
QVERIFY(profile[0]->property<QString>(Profile::Command) != QLatin1String("fish"));
}
// Verify the correct file name is created from the UntranslatedName
void ProfileTest::testProfileFileNames()
{
Profile::Ptr profile = Profile::Ptr(new Profile);
QFileInfo fileInfo;
ProfileWriter writer;
profile->setProperty(Profile::UntranslatedName, QStringLiteral("Indiana"));
fileInfo.setFile(writer.getPath(profile));
QCOMPARE(fileInfo.fileName(), QStringLiteral("Indiana.profile"));
profile->setProperty(Profile::UntranslatedName, QStringLiteral("Old Paris"));
fileInfo.setFile(writer.getPath(profile));
QCOMPARE(fileInfo.fileName(), QStringLiteral("Old Paris.profile"));
/* FIXME: deal w/ file systems that are case-insensitive
This leads to confusion as both Test and test can appear in the Manager
Profile dialog while really there is only 1 test.profile file.
Suggestions: all lowercase, testing the file system, ...
*/
/*
profile->setProperty(Profile::UntranslatedName, "New Profile");
fileInfo.setFile(writer.getPath(profile));
QCOMPARE(fileInfo.fileName(), QString("new profile.profile"));
*/
/* FIXME: don't allow certain characters in file names
Consider: ,^@=+{}[]~!?:&*\"|#%<>$\"'();`'/\
Suggestions: changing them all to _, just remove them, ...
Bug 315086 comes from a user using / in the profile name - multiple
issues there.
*/
/*
profile->setProperty(Profile::UntranslatedName, "new/profile");
fileInfo.setFile(writer.getPath(profile));
QCOMPARE(fileInfo.fileName(), QString("new_profile.profile"));
*/
}
void ProfileTest::testProfileNameSorting()
{
auto *manager = ProfileManager::instance();
const int origCount = manager->allProfiles().size();
Profile::Ptr profile1 = Profile::Ptr(new Profile);
profile1->setProperty(Profile::UntranslatedName, QStringLiteral("Indiana"));
manager->addProfile(profile1);
auto list = manager->allProfiles();
int counter = 1;
QCOMPARE(list.size(), origCount + counter++);
// Built-in profile always at the top
QCOMPARE(list.at(0)->name(), QStringLiteral("Built-in"));
QVERIFY(std::is_sorted(list.cbegin(), list.cend(), profileNameLessThan));
Profile::Ptr profile2 = Profile::Ptr(new Profile);
profile2->setProperty(Profile::UntranslatedName, QStringLiteral("Old Paris"));
manager->addProfile(profile2);
list = manager->allProfiles();
QCOMPARE(list.size(), origCount + counter++);
QVERIFY(std::is_sorted(list.cbegin(), list.cend(), profileNameLessThan));
Profile::Ptr profile3 = Profile::Ptr(new Profile);
profile3->setProperty(Profile::UntranslatedName, QStringLiteral("New Zealand"));
manager->addProfile(profile3);
list = manager->allProfiles();
QCOMPARE(list.size(), origCount + counter++);
QVERIFY(std::is_sorted(list.cbegin(), list.cend(), profileNameLessThan));
QCOMPARE(list.at(0)->name(), QStringLiteral("Built-in"));
}
void ProfileTest::testBuiltinProfile()
{
// create a new profile
Profile::Ptr builtin = Profile::Ptr(new Profile);
QVERIFY(!builtin->isBuiltin());
builtin->useBuiltin();
QVERIFY(builtin->isBuiltin());
QCOMPARE(builtin->untranslatedName(), QStringLiteral("Built-in"));
QCOMPARE(builtin->path(), QStringLiteral("FALLBACK/"));
}
void ProfileTest::testLoadProfileNamedAsBuiltin()
{
// Create a new profile data which is literally named "Built-in".
// New code should support loading such profiles created in older versions,
// but new profiles must not be saved with such name.
Profile::Ptr builtin = Profile::Ptr(new Profile);
builtin->useBuiltin();
auto profileName = QFINDTESTDATA(QStringLiteral("data/named-built-in.profile"));
QFile file(profileName);
QVERIFY(file.exists());
Profile::Ptr profile = Profile::Ptr(new Profile(builtin));
ProfileReader reader;
QString parentProfilePath;
QVERIFY(reader.readProfile(file.fileName(), profile, parentProfilePath));
QCOMPARE(profile->property<QString>(Profile::Icon), QStringLiteral("terminator"));
// It's called "Built-in", but its parent is the real built-in profile
QCOMPARE(profile->name(), builtin->name());
QCOMPARE(parentProfilePath, builtin->path());
}
void ProfileTest::testInvalidParentProfile()
{
auto *manager = ProfileManager::instance();
auto profileName = QFINDTESTDATA(QStringLiteral("data/invalid-parent.profile"));
QFile file(profileName);
QVERIFY(file.exists());
auto profile = manager->loadProfile(file.fileName());
QVERIFY(profile);
QCOMPARE(profile->property<QString>(Profile::Name), QStringLiteral("TestInvalidParentProfile"));
Profile::Ptr parent = profile->parent();
QVERIFY(parent);
QCOMPARE(parent->property<QString>(Profile::Name), QStringLiteral("Built-in"));
}
QTEST_GUILESS_MAIN(ProfileTest)
#include "moc_ProfileTest.cpp"
@@ -0,0 +1,32 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef PROFILETEST_H
#define PROFILETEST_H
#include <QObject>
namespace Konsole
{
class ProfileTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testProfile();
void testClone();
void testProfileGroup();
void testProfileFileNames();
void testProfileNameSorting();
void testBuiltinProfile();
void testLoadProfileNamedAsBuiltin();
void testInvalidParentProfile();
};
}
#endif // PROFILETEST_H
@@ -0,0 +1,90 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "PtyTest.h"
// Qt
#include <QSize>
#include <QStringList>
// KDE
#include <QTest>
using namespace Konsole;
void PtyTest::init()
{
}
void PtyTest::cleanup()
{
}
void PtyTest::testFlowControl()
{
Pty pty;
const bool input = true;
pty.setFlowControlEnabled(input);
const bool output = pty.flowControlEnabled();
QCOMPARE(output, input);
}
void PtyTest::testEraseChar()
{
Pty pty;
const char input = 'x';
pty.setEraseChar(input);
const char output = pty.eraseChar();
QCOMPARE(output, input);
}
void PtyTest::testUseUtmp()
{
Pty pty;
const bool input = true;
pty.setUseUtmp(input);
const bool output = pty.isUseUtmp();
QCOMPARE(output, input);
}
void PtyTest::testWindowSize()
{
Pty pty;
QSize input(80, 40);
QSize pxInput(80 * 8, 40 * 16);
pty.setWindowSize(input.width(), input.height(), pxInput.width(), pxInput.height());
QSize output = pty.windowSize();
QCOMPARE(output, input);
QSize pxOutput = pty.pixelSize();
QCOMPARE(pxOutput, pxInput);
}
void PtyTest::testRunProgram()
{
Pty pty;
QString program = QStringLiteral("sh");
QStringList arguments;
arguments << program;
QStringList environments;
const int result = pty.start(program, arguments, environments);
QCOMPARE(result, 0);
auto fpg = pty.foregroundProcessGroup();
auto pid = pty.processId();
// FIXME: This often fails on FreeBSD CI
// Actual (fpg): 100000; Expected (pid): 28534
#if defined(Q_OS_FREEBSD)
QSKIP("This often fails on CI FreeBSD CI", SkipSingle);
#else
QCOMPARE(fpg, pid);
#endif
pty.close();
}
QTEST_GUILESS_MAIN(PtyTest)
#include "moc_PtyTest.cpp"
@@ -0,0 +1,32 @@
/*
SPDX-FileCopyrightText: 2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef PTYTEST_H
#define PTYTEST_H
#include "../Pty.h"
namespace Konsole
{
class PtyTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void init();
void cleanup();
void testFlowControl();
void testEraseChar();
void testUseUtmp();
void testWindowSize();
void testRunProgram();
};
}
#endif // PTYTEST_H
@@ -0,0 +1,248 @@
/*
SPDX-FileCopyrightText: 2020 Lukasz Kotula <lukasz.kotula@gmx.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "ScreenTest.h"
// Qt
#include <QString>
// KDE
#include <QTest>
using namespace Konsole;
void ScreenTest::doLargeScreenCopyVerification(const QString &putToScreen, const QString &expectedSelection)
{
Screen screen(largeScreenLines, largeScreenColumns);
for (const auto &lineCharacter : putToScreen) {
screen.displayCharacter(lineCharacter.toLatin1());
}
screen.setSelectionStart(0, 0, false);
screen.setSelectionEnd(largeScreenColumns, 0, false);
QCOMPARE(screen.selectedText(Screen::PlainText), expectedSelection);
}
void ScreenTest::testLargeScreenCopyShortLine()
{
const QString putToScreen = QStringLiteral("0123456789abcde");
const QString expectedSelection = QStringLiteral("0123456789abcde\n");
doLargeScreenCopyVerification(putToScreen, expectedSelection);
}
void ScreenTest::testBlockSelection()
{
Screen screen(largeScreenLines, largeScreenColumns);
const QString reallyBigTextForReflow = QStringLiteral("abcd efgh ijkl mnop qrst uvxz ABCD EFGH IJKL MNOP QRST UVXZ");
for (const QChar &c : reallyBigTextForReflow) {
screen.displayCharacter(c.toLatin1());
}
// this breaks the lines in `abcd efgh `
// reflowing everything to the lines below.
screen.setReflowLines(true);
// reflow does not reflows cursor line, so let's move it a bit down.
screen.cursorDown(1);
screen.resizeImage(largeScreenLines, 10);
// True here means block selection.
screen.setSelectionStart(0, 0, true);
screen.setSelectionEnd(3, 1, false);
// after the resize, the string should be:
// abcd efgh
// ijkl mnop
// ...
// I'm selecting the first two lines of the first column of strings,
// so, abcd ijkl.
const QString selectedText = screen.selectedText(Screen::PlainText);
QCOMPARE(screen.selectedText(Screen::PlainText), QStringLiteral("abcd ijkl"));
}
void ScreenTest::testCJKBlockSelection()
{
Screen screen(largeScreenLines, largeScreenColumns);
const QString reallyBigTextForReflow = QStringLiteral(
// Precomposed Hangul (NFC, each syllable block is a codepoint)
"챠트 피면 술컵"
"01234567890123"
" 도 유효작 "
"01234567890123"
// Decomposed Hangul (NFD, syllables are made of several jamos)
"챠트 피면 술컵"
"01234567890123"
" 도 유효작 "
// Iroha (a pangrammic Japanese poem)
"いろはにほへと"
"01234567890123"
" ちりぬるを "
"01234567890123"
"わかよたれそ "
"01234567890123"
" つねならむ "
"01234567890123"
"うゐのおくやま"
"01234567890123"
" けふこえて "
"01234567890123"
"あさきゆめみし"
"01234567890123"
"ゑひもせす");
for (const QChar &c : reallyBigTextForReflow) {
screen.displayCharacter(c.unicode());
}
// this breaks the text so it looks like above
screen.setReflowLines(true);
// reflow does not reflows cursor line, so let's move it a bit down.
screen.cursorDown(1);
screen.resizeImage(32, 14);
// True here means block selection.
screen.setSelectionStart(2, 0, true);
screen.setSelectionEnd(6, 15, false);
// Do a block selection and compare the result to a known good result
QCOMPARE(screen.selectedText(Screen::PlainText),
QStringLiteral("\uD2B8 \uD53C 23456 \uC720\uD6A8 23456 \u1110\u1173 \u1111\u1175 23456 \u110B\u1172\u1112\u116D \u308D\u306F\u306B 23456 "
"\u308A\u306C 23456 \u304B\u3088\u305F 23456 \u306D\u306A 23456 \u3090\u306E\u304A"));
}
void ScreenTest::testLargeScreenCopyEmptyLine()
{
const QString putToScreen;
const QString expectedSelection = QStringLiteral("\n");
doLargeScreenCopyVerification(putToScreen, expectedSelection);
}
void ScreenTest::testLargeScreenCopyLongLine()
{
QString putToScreen;
// Make the line longer than screen size (1300 characters)
for (int i = 0; i < 130; ++i) {
putToScreen.append(QStringLiteral("0123456789"));
}
const QString expectedSelection = putToScreen.left(1200);
doLargeScreenCopyVerification(putToScreen, expectedSelection);
}
void ScreenTest::doComparePosition(Screen *screen, int y, int x)
{
QCOMPARE(screen->getCursorY(), y);
QCOMPARE(screen->getCursorX(), x);
}
// Test: setCursorYX, setCursorX, setCursorY, cursorDown, cursorUp,
// cursorRight, cursorLeft, cursorNextLine and cursorPreviousLine
void ScreenTest::testCursorPosition()
{
Screen *screen = new Screen(largeScreenLines, largeScreenColumns);
// setCursorYX will test setCursorX and setCursorY too
screen->setCursorYX(6, 6);
doComparePosition(screen, 5, 5);
screen->setCursorYX(2147483647, 2147483647);
doComparePosition(screen, largeScreenLines - 1, largeScreenColumns - 1);
screen->setCursorYX(-1, -1);
doComparePosition(screen, 0, 0);
screen->setCursorYX(0, 0);
doComparePosition(screen, 0, 0);
screen->setCursorYX(1, 1);
doComparePosition(screen, 0, 0);
screen->cursorDown(2147483647);
doComparePosition(screen, largeScreenLines - 1, 0);
screen->cursorUp(2147483647);
doComparePosition(screen, 0, 0);
screen->cursorDown(4);
doComparePosition(screen, 4, 0);
screen->cursorDown(-1);
doComparePosition(screen, 5, 0);
screen->cursorDown(0);
doComparePosition(screen, 6, 0);
screen->cursorUp(0);
doComparePosition(screen, 5, 0);
screen->cursorUp(-1);
doComparePosition(screen, 4, 0);
screen->cursorUp(4);
doComparePosition(screen, 0, 0);
screen->cursorRight(-1);
doComparePosition(screen, 0, 1);
screen->cursorRight(3);
doComparePosition(screen, 0, 4);
screen->cursorRight(0);
doComparePosition(screen, 0, 5);
screen->cursorLeft(0);
doComparePosition(screen, 0, 4);
screen->cursorLeft(2);
doComparePosition(screen, 0, 2);
screen->cursorLeft(-1);
doComparePosition(screen, 0, 1);
screen->cursorRight(2147483647);
doComparePosition(screen, 0, largeScreenColumns - 1);
screen->cursorLeft(2147483647);
doComparePosition(screen, 0, 0);
screen->cursorNextLine(4);
doComparePosition(screen, 4, 0);
screen->cursorNextLine(-1);
doComparePosition(screen, 5, 0);
screen->cursorNextLine(0);
doComparePosition(screen, 6, 0);
screen->cursorPreviousLine(0);
doComparePosition(screen, 5, 0);
screen->cursorPreviousLine(2);
doComparePosition(screen, 3, 0);
screen->cursorPreviousLine(-1);
doComparePosition(screen, 2, 0);
screen->cursorPreviousLine(2147483647);
doComparePosition(screen, 0, 0);
screen->cursorNextLine(2147483647);
doComparePosition(screen, largeScreenLines - 1, 0);
delete screen;
}
QTEST_GUILESS_MAIN(ScreenTest)
#include "moc_ScreenTest.cpp"

Some files were not shown because too many files have changed in this diff Show More