Files
konsole/src/MainWindow.cpp
Nate Graham 9acc879b64 Make wasWindowGeometrySaved() function consider new key format
The key format is changing in
https://invent.kde.org/frameworks/kconfig/-/merge_requests/184; take
that into account here.

Currently this function copies code from KWindowConfig, which is not
ideal; a more robust solution would be to expose the config file key
value publicly using a function in KWindowConfig that Konsole can call.
That's KF6 material, though, so for now, let's fix this issue by simply
supporting the new key format as well as the old one.
2023-02-26 00:23:12 +00:00

1176 lines
46 KiB
C++

/*
SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Own
#include "MainWindow.h"
#include "AppColorSchemeChooser.h"
// Qt
#include <QMenu>
#include <QMenuBar>
#include <QMouseEvent>
#include <QScreen>
#include <QWindow>
// KDE
#include <KAcceleratorManager>
#include <KActionCollection>
#include <KActionMenu>
#include <KCrash>
#include <KHamburgerMenu>
#include <KIconUtils>
#include <KLocalizedString>
#include <KShortcutsDialog>
#include <KToolBar>
#include <KWindowEffects>
#include <kwindowsystem_version.h>
#if HAVE_X11
#if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5, 101, 0)
#include <KX11Extras>
#endif
#endif
#include <KMessageBox>
#include <KNotifyConfigWidget>
#include <KStandardAction>
#include <KStandardGuiItem>
#include <KWindowSystem>
#include <KXMLGUIFactory>
#include <kio_version.h>
#include <kwidgetsaddons_version.h>
// Konsole
#include "BookmarkHandler.h"
#include "KonsoleSettings.h"
#include "ViewManager.h"
#include "WindowSystemInfo.h"
#include "profile/ProfileList.h"
#include "profile/ProfileManager.h"
#include "session/Session.h"
#include "session/SessionController.h"
#include "session/SessionManager.h"
#include "settings/ConfigurationDialog.h"
#include "settings/GeneralSettings.h"
#include "settings/ProfileSettings.h"
#include "settings/TabBarSettings.h"
#include "settings/TemporaryFilesSettings.h"
#include "settings/ThumbnailsSettings.h"
#include "terminalDisplay/TerminalDisplay.h"
#include "widgets/ViewContainer.h"
#include <konsoledebug.h>
using namespace Konsole;
MainWindow::MainWindow()
: KXmlGuiWindow()
, _viewManager(nullptr)
, _bookmarkHandler(nullptr)
, _toggleMenuBarAction(nullptr)
, _newTabMenuAction(nullptr)
, _pluggedController(nullptr)
{
// Set the WA_NativeWindow attribute to force the creation of the QWindow.
// Without this QWidget::windowHandle() returns 0.
// See https://phabricator.kde.org/D23108
setAttribute(Qt::WA_NativeWindow);
updateUseTransparency();
// create actions for menus
setupActions();
// create view manager
_viewManager = new ViewManager(this, actionCollection());
connect(_viewManager, &Konsole::ViewManager::empty, this, &QWidget::close);
// QueuedConnection so that KHamburgerMenu showed up properly on the first tab
connect(_viewManager, &Konsole::ViewManager::activeViewChanged, this, &Konsole::MainWindow::activeViewChanged, Qt::QueuedConnection);
connect(_viewManager, &Konsole::ViewManager::unplugController, this, &Konsole::MainWindow::disconnectController);
connect(_viewManager, &Konsole::ViewManager::viewPropertiesChanged, bookmarkHandler(), &Konsole::BookmarkHandler::setViews);
connect(_viewManager, &Konsole::ViewManager::blurSettingChanged, this, &Konsole::MainWindow::setBlur);
connect(_viewManager, &Konsole::ViewManager::updateWindowIcon, this, &Konsole::MainWindow::updateWindowIcon);
connect(_viewManager, &Konsole::ViewManager::newViewWithProfileRequest, this, &Konsole::MainWindow::newFromProfile);
connect(_viewManager, &Konsole::ViewManager::newViewRequest, this, &Konsole::MainWindow::newTab);
connect(_viewManager, &Konsole::ViewManager::terminalsDetached, this, &Konsole::MainWindow::terminalsDetached);
connect(_viewManager, &Konsole::ViewManager::activationRequest, this, &Konsole::MainWindow::activationRequest);
setCentralWidget(_viewManager->widget());
// disable automatically generated accelerators in top-level
// menu items - to avoid conflicting with Alt+[Letter] shortcuts
// in terminal applications
KAcceleratorManager::setNoAccel(menuBar());
constexpr KXmlGuiWindow::StandardWindowOptions guiOpts = ToolBar | Keys | Save | Create;
const QString xmlFile = componentName() + QLatin1String("ui.rc"); // Typically "konsoleui.rc"
// The "Create" flag will make it call createGUI()
setupGUI(guiOpts, xmlFile);
// remember the original menu accelerators for later use
rememberMenuAccelerators();
// replace standard shortcuts which cannot be used in a terminal
// emulator (as they are reserved for use by terminal applications)
correctStandardShortcuts();
setProfileList(new ProfileList(true, this));
// this must come at the end
applyKonsoleSettings();
connect(KonsoleSettings::self(), &Konsole::KonsoleSettings::configChanged, this, &Konsole::MainWindow::applyKonsoleSettings);
KCrash::initialize();
}
// Convenience function to get a space-separated list of all connected screens, copied from KWindowConfig
static QString allConnectedScreens()
{
QStringList names;
const auto screens = QGuiApplication::screens();
names.reserve(screens.length());
for (auto screen : screens) {
#ifdef Q_OS_WIN
// QScreen::name() returns garbage on Windows; see https://bugreports.qt.io/browse/QTBUG-74317
// So we use the screens' serial numbers to identify them instead
names << screen->serialNumber();
#else
names << screen->name();
#endif
}
return names.join(QLatin1Char(' '));
}
// Convenience function to get an appropriate config file key under which to
// save window size, position, or maximization information.
// Copied from KWindowConfig before https://invent.kde.org/frameworks/kconfig/-/merge_requests/184
// changed the key format. TODO: use the public function in KWindowConfig for
// this once it exists.
static QString configFileStringV1(const QScreen *screen, const QString &key)
{
// We include resolution data to also save data on a per-resolution basis
const QString returnString =
QStringLiteral("%1 %2 %3x%4 %5")
.arg(allConnectedScreens(), key, QString::number(screen->geometry().width()), QString::number(screen->geometry().height()), screen->name());
return returnString;
}
// Convenience function to get an appropriate config file key under which to
// save window size, position, or maximization information.
// Copied from KWindowConfig as of https://invent.kde.org/frameworks/kconfig/-/merge_requests/184
// TODO: use the public function in KWindowConfig for this once it exists.
static QString configFileStringV2(const QScreen *screen, const QString &key)
{
Q_UNUSED(screen);
QString returnString;
const int numberOfScreens = QGuiApplication::screens().length();
if (numberOfScreens == 1) {
// For single-screen setups, we save data on a per-resolution basis.
const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
returnString = QStringLiteral("%1x%2 screen: %3").arg(QString::number(screenGeometry.width()), QString::number(screenGeometry.height()), key);
} else {
// For multi-screen setups, we save data based on the number of screens.
// Distinguishing individual screens based on their names is unreliable
// due to name strings being inherently volatile.
returnString = QStringLiteral("%1 screens: %2").arg(QString::number(numberOfScreens), key);
}
return returnString;
}
bool MainWindow::wasWindowGeometrySaved() const
{
KSharedConfigPtr konsoleConfig = KSharedConfig::openConfig(QStringLiteral("konsolerc"));
KConfigGroup cg = konsoleConfig->group(QStringLiteral("MainWindow"));
if (!cg.exists()) { // First run, no existing konsolerc?
return false;
}
return cg.hasKey(configFileStringV2(screen(), QStringLiteral("Width"))) || cg.hasKey(configFileStringV2(screen(), QStringLiteral("Height")))
|| cg.hasKey(configFileStringV2(screen(), QStringLiteral("XPosition"))) || cg.hasKey(configFileStringV2(screen(), QStringLiteral("YPosition")))
|| cg.hasKey(configFileStringV1(screen(), QStringLiteral("Width"))) || cg.hasKey(configFileStringV1(screen(), QStringLiteral("Height")))
|| cg.hasKey(configFileStringV1(screen(), QStringLiteral("XPosition"))) || cg.hasKey(configFileStringV1(screen(), QStringLiteral("YPosition")));
}
void MainWindow::updateUseTransparency()
{
if (!WindowSystemInfo::HAVE_TRANSPARENCY) {
return;
}
bool useTranslucency = WindowSystemInfo::compositingActive();
setAttribute(Qt::WA_TranslucentBackground, useTranslucency);
setAttribute(Qt::WA_NoSystemBackground, false);
WindowSystemInfo::HAVE_TRANSPARENCY = useTranslucency;
}
void MainWindow::activationRequest(const QString &xdgActivationToken)
{
KWindowSystem::setCurrentXdgActivationToken(xdgActivationToken);
#if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5, 101, 0)
if (KWindowSystem::isPlatformX11()) {
#if HAVE_X11
KX11Extras::forceActiveWindow(winId());
#endif
} else {
KWindowSystem::activateWindow(windowHandle());
}
#else
KWindowSystem::forceActiveWindow(winId());
#endif
}
void MainWindow::rememberMenuAccelerators()
{
const QList<QAction *> actions = menuBar()->actions();
for (QAction *menuItem : actions) {
QString itemText = menuItem->text();
menuItem->setData(itemText);
}
}
// remove accelerators for standard menu items (eg. &File, &View, &Edit)
// etc. which are defined in kdelibs/kdeui/xmlgui/ui_standards.rc, again,
// to avoid conflicting with Alt+[Letter] terminal shortcuts
//
// TODO - Modify XMLGUI so that it allows the text for standard actions
// defined in ui_standards.rc to be re-defined in the local application
// XMLGUI file (konsoleui.rc in this case) - the text for standard items
// can then be redefined there to exclude the standard accelerators
void MainWindow::removeMenuAccelerators()
{
const QList<QAction *> actions = menuBar()->actions();
for (QAction *menuItem : actions) {
menuItem->setText(menuItem->text().replace(QLatin1Char('&'), QString()));
}
}
void MainWindow::restoreMenuAccelerators()
{
const QList<QAction *> actions = menuBar()->actions();
for (QAction *menuItem : actions) {
QString itemText = menuItem->data().toString();
menuItem->setText(itemText);
}
}
void MainWindow::correctStandardShortcuts()
{
// replace F1 shortcut for help contents
QAction *helpAction = actionCollection()->action(QStringLiteral("help_contents"));
if (helpAction != nullptr) {
actionCollection()->setDefaultShortcut(helpAction, QKeySequence());
}
}
ViewManager *MainWindow::viewManager() const
{
return _viewManager;
}
void MainWindow::disconnectController(SessionController *controller)
{
disconnect(controller, &Konsole::SessionController::titleChanged, this, &Konsole::MainWindow::activeViewTitleChanged);
disconnect(controller, &Konsole::SessionController::rawTitleChanged, this, &Konsole::MainWindow::updateWindowCaption);
disconnect(controller, &Konsole::SessionController::iconChanged, this, &Konsole::MainWindow::updateWindowIcon);
if (auto view = controller->view()) {
view->removeEventFilter(this);
}
// KXmlGuiFactory::removeClient() will try to access actions associated
// with the controller internally, which may not be valid after the controller
// itself is no longer valid (after the associated session and or view have
// been destroyed)
if (controller->isValid()) {
guiFactory()->removeClient(controller);
}
if (_pluggedController == controller) {
_pluggedController = nullptr;
}
}
void MainWindow::activeViewChanged(SessionController *controller)
{
if (!SessionManager::instance()->sessionProfile(controller->session())) {
return;
}
// associate bookmark menu with current session
bookmarkHandler()->setActiveView(controller);
disconnect(bookmarkHandler(), &Konsole::BookmarkHandler::openUrl, nullptr, nullptr);
connect(bookmarkHandler(), &Konsole::BookmarkHandler::openUrl, controller, &Konsole::SessionController::openUrl);
if (!_pluggedController.isNull()) {
disconnectController(_pluggedController);
}
Q_ASSERT(controller);
_pluggedController = controller;
_pluggedController->view()->installEventFilter(this);
setBlur(ViewManager::profileHasBlurEnabled(SessionManager::instance()->sessionProfile(_pluggedController->session())));
// listen for title changes from the current session
connect(controller, &Konsole::SessionController::titleChanged, this, &Konsole::MainWindow::activeViewTitleChanged);
connect(controller, &Konsole::SessionController::rawTitleChanged, this, &Konsole::MainWindow::updateWindowCaption);
connect(controller, &Konsole::SessionController::iconChanged, this, &Konsole::MainWindow::updateWindowIcon);
// to prevent shortcuts conflict
if (auto hamburgerMenu = _hamburgerMenu->menu()) {
hamburgerMenu->clear();
}
controller->setShowMenuAction(_toggleMenuBarAction);
guiFactory()->addClient(controller);
// update session title to match newly activated session
activeViewTitleChanged(controller);
// Update window icon to newly activated session's icon
updateWindowIcon();
for (IKonsolePlugin *plugin : _plugins) {
plugin->activeViewChanged(controller, this);
}
}
void MainWindow::activeViewTitleChanged(ViewProperties *properties)
{
Q_UNUSED(properties)
updateWindowCaption();
}
void MainWindow::updateWindowCaption()
{
if (_pluggedController.isNull()) {
return;
}
const QString &title = _pluggedController->title();
const QString &userTitle = _pluggedController->userTitle();
// use tab title as caption by default
QString caption = title;
// use window title as caption when this setting is enabled
// if the userTitle is empty, use a blank space (using an empty string
// removes the dash — before the application name; leaving the dash
// looks better)
if (KonsoleSettings::showWindowTitleOnTitleBar()) {
!userTitle.isEmpty() ? caption = userTitle : caption = QStringLiteral(" ");
}
setCaption(caption);
}
void MainWindow::updateWindowIcon()
{
if ((!_pluggedController.isNull()) && !_pluggedController->icon().isNull()) {
setWindowIcon(_pluggedController->icon());
}
}
void MainWindow::setupActions()
{
KActionCollection *collection = actionCollection();
// File Menu
_newTabMenuAction = new KActionMenu(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@action:inmenu", "&New Tab"), collection);
collection->setDefaultShortcut(_newTabMenuAction, Konsole::ACCEL | Qt::Key_T);
collection->setShortcutsConfigurable(_newTabMenuAction, true);
_newTabMenuAction->setAutoRepeat(false);
connect(_newTabMenuAction, &KActionMenu::triggered, this, &MainWindow::newTab);
collection->addAction(QStringLiteral("new-tab"), _newTabMenuAction);
collection->setShortcutsConfigurable(_newTabMenuAction, true);
QAction *menuAction = collection->addAction(QStringLiteral("clone-tab"));
menuAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-duplicate")));
menuAction->setText(i18nc("@action:inmenu", "&Clone Tab"));
collection->setDefaultShortcut(menuAction, QKeySequence());
menuAction->setAutoRepeat(false);
connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::cloneTab);
menuAction = collection->addAction(QStringLiteral("new-window"));
menuAction->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
menuAction->setText(i18nc("@action:inmenu", "New &Window"));
collection->setDefaultShortcut(menuAction, Konsole::ACCEL | Qt::Key_N);
menuAction->setAutoRepeat(false);
connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::newWindow);
menuAction = collection->addAction(QStringLiteral("close-window"));
menuAction->setIcon(QIcon::fromTheme(QStringLiteral("window-close")));
menuAction->setText(i18nc("@action:inmenu", "Close Window"));
collection->setDefaultShortcut(menuAction, Konsole::ACCEL | Qt::Key_Q);
connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::close);
// Bookmark Menu
KActionMenu *bookmarkMenu = new KActionMenu(i18nc("@title:menu", "&Bookmarks"), collection);
bookmarkMenu->setPopupMode(QToolButton::InstantPopup); // Don't require press+hold
_bookmarkHandler = new BookmarkHandler(collection, bookmarkMenu->menu(), true, this);
collection->addAction(QStringLiteral("bookmark"), bookmarkMenu);
connect(_bookmarkHandler, &Konsole::BookmarkHandler::openUrls, this, &Konsole::MainWindow::openUrls);
// Settings Menu
_toggleMenuBarAction = KStandardAction::showMenubar(menuBar(), &QMenuBar::setVisible, collection);
collection->setDefaultShortcut(_toggleMenuBarAction, Konsole::ACCEL | Qt::Key_M);
connect(_toggleMenuBarAction, &QAction::triggered, [=] {
// Remove menubar icons set for the hamburger menu, so they don't override
// the text when they appear in the in-window menubar
collection->action(QStringLiteral("bookmark"))->setIcon(QIcon());
static_cast<QMenu *>(factory()->container(QStringLiteral("plugins"), this))->setIcon(QIcon());
});
// Set up themes
actionCollection()->addAction(QStringLiteral("window-colorscheme-menu"), new AppColorSchemeChooser(actionCollection()));
// Full Screen
menuAction = KStandardAction::fullScreen(this, &MainWindow::viewFullScreen, this, collection);
collection->setDefaultShortcut(menuAction, Qt::Key_F11);
KStandardAction::configureNotifications(this, &MainWindow::configureNotifications, collection);
KStandardAction::keyBindings(this, &MainWindow::showShortcutsDialog, collection);
KStandardAction::preferences(this, &MainWindow::showSettingsDialog, collection);
menuAction = collection->addAction(QStringLiteral("manage-profiles"));
menuAction->setText(i18nc("@action:inmenu", "Manage Profiles..."));
menuAction->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::showManageProfilesDialog);
// Set up an shortcut-only action for activating menu bar.
menuAction = collection->addAction(QStringLiteral("activate-menu"));
menuAction->setText(i18nc("@item", "Activate Menu"));
collection->setDefaultShortcut(menuAction, Konsole::ACCEL | Qt::Key_F10);
connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::activateMenuBar);
auto action = collection->addAction(QStringLiteral("save-layout"));
action->setEnabled(true);
action->setText(i18nc("@action:inmenu", "Save Tab Layout..."));
connect(action, &QAction::triggered, this, [this]() {
if (viewManager()) {
viewManager()->saveLayoutFile();
}
});
action = collection->addAction(QStringLiteral("load-layout"));
action->setEnabled(true);
action->setText(i18nc("@action:inmenu", "Load Tab Layout..."));
connect(action, &QAction::triggered, this, [this]() {
if (viewManager()) {
viewManager()->loadLayoutFile();
}
});
// Hamburger menu for when the menubar is hidden
_hamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, collection);
_hamburgerMenu->setShowMenuBarAction(_toggleMenuBarAction);
_hamburgerMenu->setMenuBar(menuBar());
connect(_hamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, &MainWindow::updateHamburgerMenu);
}
void MainWindow::updateHamburgerMenu()
{
KActionCollection *collection = actionCollection();
KActionCollection *controllerCollection = _pluggedController->actionCollection();
QMenu *menu = _hamburgerMenu->menu();
if (!menu) {
menu = new QMenu(widget());
_hamburgerMenu->setMenu(menu);
} else {
menu->clear();
}
menu->addAction(collection->action(QStringLiteral("new-window")));
menu->addAction(collection->action(QStringLiteral("new-tab")));
menu->addAction(controllerCollection->action(QStringLiteral("file_save_as")));
menu->addSeparator();
menu->addAction(controllerCollection->action(QStringLiteral("edit_copy")));
menu->addAction(controllerCollection->action(QStringLiteral("edit_paste")));
menu->addAction(controllerCollection->action(QStringLiteral("edit_find")));
menu->addSeparator();
menu->addAction(collection->action(QLatin1String(KStandardAction::name(KStandardAction::FullScreen))));
menu->addAction(collection->action(QStringLiteral("split-view")));
menu->addAction(controllerCollection->action(QStringLiteral("clear-history-and-reset")));
menu->addAction(controllerCollection->action(QStringLiteral("enlarge-font")));
menu->addAction(controllerCollection->action(QStringLiteral("reset-font-size")));
menu->addAction(controllerCollection->action(QStringLiteral("shrink-font")));
menu->addSeparator();
menu->addAction(controllerCollection->action(QStringLiteral("edit-current-profile")));
menu->addAction(controllerCollection->action(QStringLiteral("switch-profile")));
menu->addSeparator();
auto monitorMenu = menu->addMenu(QIcon::fromTheme(QStringLiteral("visibility")), static_cast<QMenu *>(factory()->container(QStringLiteral("view"), nullptr))->title());
monitorMenu->addAction(controllerCollection->action(QStringLiteral("monitor-silence")));
monitorMenu->addAction(controllerCollection->action(QStringLiteral("monitor-activity")));
monitorMenu->addAction(controllerCollection->action(QStringLiteral("monitor-process-finish")));
menu->addMenu(monitorMenu);
_hamburgerMenu->hideActionsOf(monitorMenu);
auto bookmarkMenu = collection->action(QStringLiteral("bookmark"));
bookmarkMenu->setIcon(QIcon::fromTheme(QStringLiteral("bookmarks"))); // Icon will be removed again when the menu bar is enabled.
menu->addAction(bookmarkMenu);
auto pluginsMenu = static_cast<QMenu *>(factory()->container(QStringLiteral("plugins"), this));
pluginsMenu->setIcon(QIcon::fromTheme(QStringLiteral("plugins"))); // Icon will be removed again when the menu bar is enabled.
menu->addMenu(pluginsMenu);
auto configureMenu = menu->addMenu(QIcon::fromTheme(QStringLiteral("configure")), static_cast<QMenu *>(factory()->container(QStringLiteral("settings"), nullptr))->title());
configureMenu->addAction(toolBarMenuAction());
configureMenu->addSeparator();
configureMenu->addAction(collection->action(QLatin1String(KStandardAction::name(KStandardAction::SwitchApplicationLanguage))));
configureMenu->addAction(collection->action(QLatin1String(KStandardAction::name(KStandardAction::KeyBindings))));
configureMenu->addAction(collection->action(QLatin1String(KStandardAction::name(KStandardAction::ConfigureToolbars))));
configureMenu->addAction(collection->action(QLatin1String(KStandardAction::name(KStandardAction::ConfigureNotifications))));
configureMenu->addAction(collection->action(QLatin1String(KStandardAction::name(KStandardAction::Preferences))));
_hamburgerMenu->hideActionsOf(configureMenu);
_hamburgerMenu->hideActionsOf(toolBar());
}
void MainWindow::viewFullScreen(bool fullScreen)
{
if (fullScreen) {
setWindowState(windowState() | Qt::WindowFullScreen);
} else {
setWindowState(windowState() & ~Qt::WindowFullScreen);
}
}
void MainWindow::applyMainWindowSettings(const KConfigGroup &config)
{
KMainWindow::applyMainWindowSettings(config);
// Override the menubar state from the config file
if (_windowArgsMenuBarVisible.enabled) {
menuBar()->setVisible(_windowArgsMenuBarVisible.showMenuBar);
}
_toggleMenuBarAction->setChecked(menuBar()->isVisibleTo(this));
}
BookmarkHandler *MainWindow::bookmarkHandler() const
{
return _bookmarkHandler;
}
void MainWindow::setProfileList(ProfileList *list)
{
profileListChanged(list->actions());
connect(list, &Konsole::ProfileList::profileSelected, this, &MainWindow::newFromProfile);
connect(list, &Konsole::ProfileList::actionsChanged, this, &Konsole::MainWindow::profileListChanged);
}
void MainWindow::profileListChanged(const QList<QAction *> &sessionActions)
{
// Update the 'New Tab' KActionMenu
_newTabMenuAction->menu()->clear();
for (QAction *sessionAction : sessionActions) {
_newTabMenuAction->menu()->addAction(sessionAction);
auto setActionFontBold = [sessionAction](bool isBold) {
QFont actionFont = sessionAction->font();
actionFont.setBold(isBold);
sessionAction->setFont(actionFont);
};
Profile::Ptr profile = ProfileManager::instance()->defaultProfile();
if (profile && profile->name() == sessionAction->text().remove(QLatin1Char('&'))) {
QIcon icon = KIconUtils::addOverlay(QIcon::fromTheme(profile->icon()), QIcon::fromTheme(QStringLiteral("emblem-favorite")), Qt::BottomRightCorner);
sessionAction->setIcon(icon);
setActionFontBold(true);
} else {
setActionFontBold(false);
}
}
}
QString MainWindow::activeSessionDir() const
{
if (!_pluggedController.isNull()) {
if (Session *session = _pluggedController->session()) {
// For new tabs to get the correct working directory,
// force the updating of the currentWorkingDirectory.
session->getDynamicTitle();
}
return _pluggedController->currentDir();
}
return QString();
}
void MainWindow::openUrls(const QList<QUrl> &urls)
{
Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile();
for (const auto &url : urls) {
if (url.isLocalFile()) {
createSession(defaultProfile, url.path());
} else if (url.scheme() == QLatin1String("ssh")) {
createSSHSession(defaultProfile, url);
}
}
}
void MainWindow::newTab()
{
Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile();
createSession(defaultProfile, activeSessionDir());
}
void MainWindow::addPlugin(IKonsolePlugin *plugin)
{
Q_ASSERT(std::find(_plugins.cbegin(), _plugins.cend(), plugin) == _plugins.cend());
_plugins.push_back(plugin);
}
void MainWindow::cloneTab()
{
Q_ASSERT(_pluggedController);
Session *session = _pluggedController->session();
Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
if (profile) {
createSession(profile, activeSessionDir());
} else {
// something must be wrong: every session should be associated with profile
Q_ASSERT(false);
newTab();
}
}
Session *MainWindow::createSession(Profile::Ptr profile, const QString &directory)
{
if (!profile) {
profile = ProfileManager::instance()->defaultProfile();
}
const QString newSessionDirectory = profile->startInCurrentSessionDir() ? directory : QString();
Session *session = _viewManager->createSession(profile, newSessionDirectory);
// create view before starting the session process so that the session
// doesn't suffer a change in terminal size right after the session
// starts. Some applications such as GNU Screen and Midnight Commander
// don't like this happening
auto newView = _viewManager->createView(session);
_viewManager->activeContainer()->addView(newView);
_viewManager->activeViewController()->actionCollection()->addActions(
{_hamburgerMenu});
return session;
}
Session *MainWindow::createSSHSession(Profile::Ptr profile, const QUrl &url)
{
if (!profile) {
profile = ProfileManager::instance()->defaultProfile();
}
Session *session = SessionManager::instance()->createSession(profile);
QString sshCommand = QStringLiteral("ssh ");
if (url.port() > -1) {
sshCommand += QStringLiteral("-p %1 ").arg(url.port());
}
if (!url.userName().isEmpty()) {
sshCommand += (url.userName() + QLatin1Char('@'));
}
if (!url.host().isEmpty()) {
sshCommand += url.host();
}
session->sendTextToTerminal(sshCommand, QLatin1Char('\r'));
// create view before starting the session process so that the session
// doesn't suffer a change in terminal size right after the session
// starts. some applications such as GNU Screen and Midnight Commander
// don't like this happening
auto newView = _viewManager->createView(session);
_viewManager->activeContainer()->addView(newView);
return session;
}
void MainWindow::setFocus()
{
_viewManager->activeView()->setFocus();
}
void MainWindow::newWindow()
{
Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile();
Q_EMIT newWindowRequest(defaultProfile, activeSessionDir());
}
bool MainWindow::queryClose()
{
// Do not ask for confirmation during log out and power off
// TODO: rework the dealing of this case to make it has its own confirmation
// dialog.
if (qApp->isSavingSession()) {
return true;
}
// Check what processes are running, excluding the shell
QStringList processesRunning;
// Need to make a local copy so the begin() and end() point to the same QList
const QList<Session *> sessionList = _viewManager->sessions();
const QSet<Session *> uniqueSessions(sessionList.begin(), sessionList.end());
for (Session *session : uniqueSessions) {
if ((session == nullptr) || !session->isForegroundProcessActive()) {
continue;
}
const QString defaultProc = session->program().split(QLatin1Char('/')).last();
const QString currentProc = session->foregroundProcessName().split(QLatin1Char('/')).last();
if (currentProc.isEmpty()) {
continue;
}
if (defaultProc != currentProc) {
processesRunning.append(currentProc);
}
}
// Get number of open tabs
const int openTabs = _viewManager->viewProperties().count();
// If no processes running (except the shell) and no extra tabs, just close
if (processesRunning.count() == 0 && openTabs < 2) {
return true;
}
// NOTE: Some, if not all, of the below KWindowSystem calls are only
// implemented under x11 (KDE4.8 kdelibs/kdeui/windowmanagement).
#if HAVE_X11
// make sure the window is shown on current desktop and is not minimized
#if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5, 101, 0)
KX11Extras::setOnDesktop(winId(), KX11Extras::currentDesktop());
#else
KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop());
#endif
#endif
int result;
if (!processesRunning.isEmpty()) {
if (openTabs == 1) {
#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
result = KMessageBox::warningTwoActionsList(this,
#else
result = KMessageBox::warningYesNoList(this,
#endif
i18ncp("@info",
"There is a process running in this window. "
"Do you still want to quit?",
"There are %1 processes running in this window. "
"Do you still want to quit?",
processesRunning.count()),
processesRunning,
i18nc("@title", "Confirm Close"),
KGuiItem(i18nc("@action:button", "Close &Window"), QStringLiteral("window-close")),
KStandardGuiItem::cancel(),
// don't ask again name is wrong but I can't update.
// this is not about tabs anymore. it's about empty tabs *or* splits.
QStringLiteral("CloseAllTabs"));
#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
if (result == KMessageBox::SecondaryAction) // SecondaryAction is equal to cancel closing
#else
if (result == KMessageBox::No) // No is equal to cancel closing
#endif
result = KMessageBox::Cancel;
} else {
#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
result = KMessageBox::warningTwoActionsCancelList(this,
#else
result = KMessageBox::warningYesNoCancelList(this,
#endif
i18ncp("@info",
"There is a process running in this window. "
"Do you still want to quit?",
"There are %1 processes running in this window. "
"Do you still want to quit?",
processesRunning.count()),
processesRunning,
i18nc("@title", "Confirm Close"),
KGuiItem(i18nc("@action:button", "Close &Window"), QStringLiteral("window-close")),
KGuiItem(i18nc("@action:button", "Close Current &Tab"), QStringLiteral("tab-close")),
KStandardGuiItem::cancel(),
// don't ask again name is wrong but I can't update.
// this is not about tabs anymore. it's about empty tabs *or* splits.
QStringLiteral("CloseAllTabs"));
}
} else {
#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
result = KMessageBox::warningTwoActionsCancel(this,
#else
result = KMessageBox::warningYesNoCancel(this,
#endif
i18nc("@info",
"There are %1 open terminals in this window. "
"Do you still want to quit?",
openTabs),
i18nc("@title", "Confirm Close"),
KGuiItem(i18nc("@action:button", "Close &Window"), QStringLiteral("window-close")),
KGuiItem(i18nc("@action:button", "Close Current &Tab"), QStringLiteral("tab-close")),
KStandardGuiItem::cancel(),
// don't ask again name is wrong but I can't update.
// this is not about tabs anymore. it's about empty tabs *or* splits.
QStringLiteral("CloseAllEmptyTabs"));
}
switch (result) {
#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
case KMessageBox::PrimaryAction:
#else
case KMessageBox::Yes:
#endif
return true;
#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
case KMessageBox::SecondaryAction:
#else
case KMessageBox::No:
#endif
if ((!_pluggedController.isNull()) && (!_pluggedController->session().isNull())) {
if (!(_pluggedController->session()->closeInNormalWay())) {
if (_pluggedController->confirmForceClose()) {
_pluggedController->session()->closeInForceWay();
}
}
}
return false;
case KMessageBox::Cancel:
return false;
}
return true;
}
void MainWindow::saveProperties(KConfigGroup &group)
{
_viewManager->saveSessions(group);
}
void MainWindow::readProperties(const KConfigGroup &group)
{
_viewManager->restoreSessions(group);
}
void MainWindow::saveGlobalProperties(KConfig *config)
{
SessionManager::instance()->saveSessions(config);
}
void MainWindow::readGlobalProperties(KConfig *config)
{
SessionManager::instance()->restoreSessions(config);
}
void MainWindow::syncActiveShortcuts(KActionCollection *dest, const KActionCollection *source)
{
const QList<QAction *> actionsList = source->actions();
for (QAction *qAction : actionsList) {
if (QAction *destQAction = dest->action(qAction->objectName())) {
destQAction->setShortcut(qAction->shortcut());
}
}
}
void MainWindow::showShortcutsDialog()
{
KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsDisallowed, this);
// add actions from this window and the current session controller
const QList<KXMLGUIClient *> clientsList = guiFactory()->clients();
for (KXMLGUIClient *client : clientsList) {
dialog.addCollection(client->actionCollection());
}
if (dialog.configure()) {
// sync shortcuts for non-session actions (defined in "konsoleui.rc") in other main windows
const QList<QWidget *> widgets = QApplication::topLevelWidgets();
for (QWidget *mainWindowWidget : widgets) {
auto *mainWindow = qobject_cast<MainWindow *>(mainWindowWidget);
if ((mainWindow != nullptr) && mainWindow != this) {
syncActiveShortcuts(mainWindow->actionCollection(), actionCollection());
}
}
// sync shortcuts for session actions (defined in "sessionui.rc") in other session controllers.
// Controllers which are currently plugged in (ie. their actions are part of the current menu)
// must be updated immediately via syncActiveShortcuts(). Other controllers will be updated
// when they are plugged into a main window.
const QSet<SessionController *> allControllers = SessionController::allControllers();
for (SessionController *controller : allControllers) {
controller->reloadXML();
if ((controller->factory() != nullptr) && controller != _pluggedController) {
syncActiveShortcuts(controller->actionCollection(), _pluggedController->actionCollection());
}
}
}
}
void MainWindow::newFromProfile(const Profile::Ptr &profile)
{
createSession(profile, activeSessionDir());
}
void MainWindow::showManageProfilesDialog()
{
showSettingsDialog(true);
}
void MainWindow::showSettingsDialog(const bool showProfilePage)
{
ConfigurationDialog *confDialog = findChild<ConfigurationDialog *>();
const QString profilePageName = i18nc("@title Preferences page name", "Profiles");
if (confDialog != nullptr) {
if (showProfilePage) {
for (auto page : confDialog->findChildren<KPageWidgetItem *>()) {
if (page->name().contains(profilePageName)) {
confDialog->setCurrentPage(page);
break;
}
}
}
confDialog->show();
return;
}
confDialog = new ConfigurationDialog(this, KonsoleSettings::self());
const QString generalPageName = i18nc("@title Preferences page name", "General");
auto *generalPage = new KPageWidgetItem(new GeneralSettings(confDialog), generalPageName);
generalPage->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal")));
confDialog->addPage(generalPage, true);
auto *profileSettings = new ProfileSettings(confDialog);
auto *profilePage = new KPageWidgetItem(profileSettings, profilePageName);
profilePage->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-theme")));
confDialog->addPage(profilePage, true);
connect(confDialog, &QDialog::accepted, profileSettings, &ProfileSettings::slotAccepted);
const QString tabBarPageName = i18nc("@title Preferences page name", "Tab Bar / Splitters");
auto tabBarPage = new KPageWidgetItem(new TabBarSettings(confDialog), tabBarPageName);
tabBarPage->setIcon(QIcon::fromTheme(QStringLiteral("preferences-tabs")));
confDialog->addPage(tabBarPage, true);
const QString temporaryFilesPageName = i18nc("@title Preferences page name", "Temporary Files");
auto temporaryFilesPage = new KPageWidgetItem(new TemporaryFilesSettings(confDialog), temporaryFilesPageName);
temporaryFilesPage->setIcon(QIcon::fromTheme(QStringLiteral("folder-temp")));
confDialog->addPage(temporaryFilesPage, true);
const QString thumbnailPageName = i18nc("@title Preferences page name", "Thumbnails");
auto thumbnailPage = new KPageWidgetItem(new ThumbnailsSettings(confDialog), thumbnailPageName);
thumbnailPage->setIcon(QIcon::fromTheme(QStringLiteral("image-jpeg")));
confDialog->addPage(thumbnailPage, true);
if (showProfilePage) {
confDialog->setCurrentPage(profilePage);
}
confDialog->show();
}
void MainWindow::applyKonsoleSettings()
{
setRemoveWindowTitleBarAndFrame(KonsoleSettings::removeWindowTitleBarAndFrame());
if (KonsoleSettings::allowMenuAccelerators()) {
restoreMenuAccelerators();
} else {
removeMenuAccelerators();
}
_viewManager->activeContainer()->setNavigationBehavior(KonsoleSettings::newTabBehavior());
// Save the toolbar/menu/dockwidget states and the window geometry
setAutoSaveSettings();
updateWindowCaption();
}
void MainWindow::activateMenuBar()
{
const QList<QAction *> menuActions = menuBar()->actions();
if (menuActions.isEmpty()) {
return;
}
// Show menubar if it is hidden at the moment
if (menuBar()->isHidden()) {
menuBar()->setVisible(true);
_toggleMenuBarAction->setChecked(true);
}
// First menu action should be 'File'
QAction *menuAction = menuActions.first();
// TODO: Handle when menubar is top level (MacOS)
menuBar()->setActiveAction(menuAction);
}
void MainWindow::configureNotifications()
{
KNotifyConfigWidget::configure(this);
}
void MainWindow::setBlur(bool blur)
{
if (_pluggedController.isNull()) {
return;
}
// Saves 70-100ms when starting
if (blur == _blurEnabled) {
return;
}
_blurEnabled = blur;
if (!_pluggedController->isKonsolePart()) {
#if KWINDOWSYSTEM_VERSION < QT_VERSION_CHECK(5, 82, 0)
KWindowEffects::enableBlurBehind(winId(), blur);
#else
if (QWindow *window = windowHandle()) {
KWindowEffects::enableBlurBehind(window, blur);
} else {
qCWarning(KonsoleDebug) << "Blur effect couldn't be enabled.";
}
#endif
}
}
void MainWindow::setMenuBarInitialVisibility(bool showMenuBar)
{
_windowArgsMenuBarVisible.enabled = true;
_windowArgsMenuBarVisible.showMenuBar = showMenuBar;
}
void MainWindow::setRemoveWindowTitleBarAndFrame(bool frameless)
{
Qt::WindowFlags newFlags = frameless ? Qt::FramelessWindowHint : Qt::Window;
// The window is not yet visible
if (!isVisible()) {
setWindowFlags(newFlags);
// The window is visible and the setting changed
} else if (windowFlags().testFlag(Qt::FramelessWindowHint) != frameless) {
if (KWindowSystem::isPlatformX11()) {
#if HAVE_X11
const auto oldGeometry = saveGeometry();
// This happens for every Konsole window. It depends on
// the fact that every window is processed in single thread
#if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5, 101, 0)
const auto oldActiveWindow = KX11Extras::activeWindow();
#else
const auto oldActiveWindow = KWindowSystem::activeWindow();
#endif
setWindowFlags(newFlags);
// The setWindowFlags() has hidden the window. Show it again
// with previous geometry
restoreGeometry(oldGeometry);
setVisible(true);
#if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5, 101, 0)
KX11Extras::activateWindow(oldActiveWindow);
#else
KWindowSystem::activateWindow(oldActiveWindow);
#endif
#endif
} else {
// Restoring geometry ourselves doesn't work on Wayland
setWindowFlags(newFlags);
// The setWindowFlags() has hidden the window. Show it again
setVisible(true);
}
}
}
void MainWindow::showEvent(QShowEvent *event)
{
// Apply this code on first show only
if (_firstShowEvent) {
_firstShowEvent = false;
if (!KonsoleSettings::rememberWindowSize() || !wasWindowGeometrySaved()) {
// Delay resizing to here, so that the other parts of the UI
// (ViewManager, TabbedViewContainer, TerminalDisplay ... etc)
// have been created and TabbedViewContainer::sizeHint() returns
// a usable size.
// Remove the WindowMaximized state to override the Window-Maximized
// config key
if (QWindow *window = windowHandle()) {
window->setWindowStates(window->windowStates() & ~Qt::WindowMaximized);
}
resize(sizeHint());
}
}
// Call parent method
KXmlGuiWindow::showEvent(event);
}
void MainWindow::triggerAction(const QString &name) const
{
if (auto action = actionCollection()->action(name)) {
if (action->isEnabled()) {
action->trigger();
}
}
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (!_pluggedController.isNull() && obj == _pluggedController->view()) {
switch (event->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick:
switch (static_cast<QMouseEvent *>(event)->button()) {
case Qt::ForwardButton:
triggerAction(QStringLiteral("next-view"));
break;
case Qt::BackButton:
triggerAction(QStringLiteral("previous-view"));
break;
default:;
}
default:;
}
}
return KXmlGuiWindow::eventFilter(obj, event);
}
bool MainWindow::focusNextPrevChild(bool v)
{
if (qobject_cast<TerminalDisplay *>(focusWidget())) {
return false;
}
return QMainWindow::focusNextPrevChild(v);
}