File indexing completed on 2024-04-28 05:50:48

0001 /*
0002     This source file is part of Konsole, a terminal emulator.
0003 
0004     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 // Own
0010 #include "SessionManager.h"
0011 
0012 // Qt
0013 #include <QStringList>
0014 #include <QTextCodec>
0015 
0016 // KDE
0017 #include <KConfig>
0018 #include <KConfigGroup>
0019 
0020 // Konsole
0021 #include "Emulation.h" // to connect the URL escape sequence extractor
0022 #include "Enumeration.h"
0023 #include "EscapeSequenceUrlExtractor.h"
0024 #include "Screen.h"
0025 #include "ShouldApplyProperty.h"
0026 #include "konsoledebug.h"
0027 
0028 #include "history/HistoryTypeFile.h"
0029 #include "history/HistoryTypeNone.h"
0030 #include "history/compact/CompactHistoryType.h"
0031 
0032 #include "profile/ProfileCommandParser.h"
0033 #include "profile/ProfileManager.h"
0034 
0035 #include "Session.h"
0036 
0037 #include "terminalDisplay/TerminalDisplay.h"
0038 #include "terminalDisplay/TerminalFonts.h"
0039 
0040 using namespace Konsole;
0041 
0042 SessionManager::SessionManager()
0043 {
0044     ProfileManager *profileMananger = ProfileManager::instance();
0045     connect(profileMananger, &Konsole::ProfileManager::profileChanged, this, &Konsole::SessionManager::profileChanged);
0046 }
0047 
0048 SessionManager::~SessionManager()
0049 {
0050     if (!_sessions.isEmpty()) {
0051         qCDebug(KonsoleDebug) << "Konsole SessionManager destroyed with" << _sessions.count() << "session(s) still alive";
0052         // ensure that the Session doesn't later try to call back and do things to the
0053         // SessionManager
0054         for (Session *session : std::as_const(_sessions)) {
0055             disconnect(session, nullptr, this, nullptr);
0056         }
0057     }
0058 }
0059 
0060 Q_GLOBAL_STATIC(SessionManager, theSessionManager)
0061 SessionManager *SessionManager::instance()
0062 {
0063     return theSessionManager;
0064 }
0065 
0066 bool SessionManager::isClosingAllSessions() const
0067 {
0068     return _isClosingAllSessions;
0069 }
0070 
0071 void SessionManager::closeAllSessions()
0072 {
0073     _isClosingAllSessions = true;
0074     for (Session *session : std::as_const(_sessions)) {
0075         session->close();
0076     }
0077     _sessions.clear();
0078 }
0079 
0080 const QList<Session *> SessionManager::sessions() const
0081 {
0082     return _sessions;
0083 }
0084 
0085 Session *SessionManager::createSession(Profile::Ptr profile)
0086 {
0087     if (!profile) {
0088         profile = ProfileManager::instance()->defaultProfile();
0089     }
0090 
0091     // TODO: check whether this is really needed
0092     if (!ProfileManager::instance()->loadedProfiles().contains(profile)) {
0093         ProfileManager::instance()->addProfile(profile);
0094     }
0095 
0096     // configuration information found, create a new session based on this
0097     auto session = new Session();
0098     Q_ASSERT(session);
0099     applyProfile(session, profile, false);
0100 
0101     connect(session, &Konsole::Session::profileChangeCommandReceived, this, [this, session](const QString &text) {
0102         sessionProfileCommandReceived(session, text);
0103     });
0104 
0105     // ask for notification when session dies
0106     connect(session, &Konsole::Session::finished, this, &SessionManager::sessionTerminated);
0107 
0108     // add session to active list
0109     _sessions << session;
0110     _sessionProfiles.insert(session, profile);
0111 
0112     return session;
0113 }
0114 
0115 void SessionManager::profileChanged(const Profile::Ptr &profile)
0116 {
0117     applyProfile(profile, true);
0118 }
0119 
0120 void SessionManager::sessionTerminated(Session *session)
0121 {
0122     Q_ASSERT(session);
0123 
0124     _sessions.removeAll(session);
0125     _sessionProfiles.remove(session);
0126     _sessionRuntimeProfiles.remove(session);
0127 
0128     session->deleteLater();
0129 }
0130 
0131 void SessionManager::applyProfile(const Profile::Ptr &profile, bool modifiedPropertiesOnly)
0132 {
0133     for (Session *session : std::as_const(_sessions)) {
0134         if (_sessionProfiles[session] == profile) {
0135             applyProfile(session, profile, modifiedPropertiesOnly);
0136         }
0137     }
0138 }
0139 
0140 Profile::Ptr SessionManager::sessionProfile(Session *session) const
0141 {
0142     return _sessionProfiles[session];
0143 }
0144 
0145 void SessionManager::setSessionProfile(Session *session, Profile::Ptr profile)
0146 {
0147     if (!profile) {
0148         profile = ProfileManager::instance()->defaultProfile();
0149     }
0150 
0151     Q_ASSERT(profile);
0152 
0153     _sessionProfiles[session] = profile;
0154 
0155     applyProfile(session, profile, false);
0156 
0157     Q_EMIT sessionUpdated(session);
0158 }
0159 
0160 void SessionManager::applyProfile(Session *session, const Profile::Ptr &profile, bool modifiedPropertiesOnly)
0161 {
0162     Q_ASSERT(profile);
0163     _sessionProfiles[session] = profile;
0164 
0165     ShouldApplyProperty apply(profile, modifiedPropertiesOnly);
0166 
0167     // Basic session settings
0168     if (apply.shouldApply(Profile::Name)) {
0169         session->setTitle(Session::NameRole, profile->name());
0170     }
0171 
0172     if (apply.shouldApply(Profile::Command)) {
0173         session->setProgram(profile->command());
0174     }
0175 
0176     if (apply.shouldApply(Profile::Arguments)) {
0177         session->setArguments(profile->arguments());
0178     }
0179 
0180     if (apply.shouldApply(Profile::Directory)) {
0181         session->setInitialWorkingDirectory(profile->defaultWorkingDirectory());
0182     }
0183 
0184     if (apply.shouldApply(Profile::Environment)) {
0185         // add environment variable containing home directory of current profile
0186         // (if specified)
0187 
0188         // prepend a 0 to the VERSION_MICRO part to make the version string
0189         // length consistent, so that conditions that depend on the exported
0190         // env var actually work
0191         // e.g. the second version should be higher than the first one:
0192         // 18.04.12 -> 180412
0193         // 18.08.0  -> 180800
0194         QStringList list = QStringLiteral(KONSOLE_VERSION).split(QLatin1Char('.'));
0195         if (list[2].length() < 2) {
0196             list[2].prepend(QLatin1String("0"));
0197         }
0198         const QString &numericVersion = list.join(QString());
0199 
0200         QStringList environment = profile->environment();
0201         environment << QStringLiteral("PROFILEHOME=%1").arg(profile->defaultWorkingDirectory());
0202         environment << QStringLiteral("KONSOLE_VERSION=%1").arg(numericVersion);
0203 #ifdef Q_OS_WIN
0204         // Needed on windows otherwise powershell will refuse to start
0205         environment += QProcess::systemEnvironment();
0206 #endif
0207         session->setEnvironment(environment);
0208     }
0209 
0210     if (apply.shouldApply(Profile::TerminalColumns) || apply.shouldApply(Profile::TerminalRows)) {
0211         const auto highlightScrolledLines = profile->property<bool>(Profile::HighlightScrolledLines);
0212         const auto rows = profile->property<int>(Profile::TerminalRows);
0213         auto columns = profile->property<int>(Profile::TerminalColumns);
0214         // highlightScrolledLines takes 1 column to display, correct it
0215         // adding 1 to terminal initial columns profile preference
0216         columns += highlightScrolledLines ? 1 : 0;
0217         session->setPreferredSize(QSize(columns, rows));
0218     }
0219 
0220     if (apply.shouldApply(Profile::Icon)) {
0221         session->setIconName(profile->icon());
0222     }
0223 
0224     // Key bindings
0225     if (apply.shouldApply(Profile::KeyBindings)) {
0226         session->setKeyBindings(profile->keyBindings());
0227     }
0228 
0229     // Tab formats
0230     // Preserve tab title changes, made by the user, when applying profile
0231     // changes or previewing color schemes
0232     if (apply.shouldApply(Profile::LocalTabTitleFormat) && !session->isTabTitleSetByUser()) {
0233         session->setTabTitleFormat(Session::LocalTabTitle, profile->localTabTitleFormat());
0234     }
0235     if (apply.shouldApply(Profile::RemoteTabTitleFormat) && !session->isTabTitleSetByUser()) {
0236         session->setTabTitleFormat(Session::RemoteTabTitle, profile->remoteTabTitleFormat());
0237     }
0238     if (apply.shouldApply(Profile::TabColor) && !session->isTabColorSetByUser()) {
0239         session->setColor(profile->tabColor());
0240     }
0241 
0242     // History
0243     if (apply.shouldApply(Profile::HistoryMode) || apply.shouldApply(Profile::HistorySize)) {
0244         const auto mode = profile->property<int>(Profile::HistoryMode);
0245         switch (mode) {
0246         case Enum::NoHistory:
0247             session->setHistoryType(HistoryTypeNone());
0248             break;
0249 
0250         case Enum::FixedSizeHistory: {
0251             int lines = profile->historySize();
0252             session->setHistoryType(CompactHistoryType(lines));
0253             break;
0254         }
0255 
0256         case Enum::UnlimitedHistory:
0257             session->setHistoryType(HistoryTypeFile());
0258             break;
0259         }
0260     }
0261 
0262     // Terminal features
0263     if (apply.shouldApply(Profile::FlowControlEnabled)) {
0264         session->setFlowControlEnabled(profile->flowControlEnabled());
0265     }
0266 
0267     // Encoding
0268     if (apply.shouldApply(Profile::DefaultEncoding)) {
0269         QByteArray name = profile->defaultEncoding().toUtf8();
0270         session->setCodec(QTextCodec::codecForName(name));
0271     }
0272 
0273     // Monitor Silence
0274     if (apply.shouldApply(Profile::SilenceSeconds)) {
0275         session->setMonitorSilenceSeconds(profile->silenceSeconds());
0276     }
0277     if (apply.shouldApply(Profile::AllowEscapedLinks) || apply.shouldApply(Profile::ReflowLines) || apply.shouldApply(Profile::IgnoreWcWidth)) {
0278         const bool shouldEnableUrlExtractor = profile->allowEscapedLinks();
0279         const bool enableReflowLines = profile->property<bool>(Profile::ReflowLines);
0280         const bool ignoreWcWidth = profile->property<bool>(Profile::IgnoreWcWidth);
0281         for (TerminalDisplay *view : session->views()) {
0282             view->screenWindow()->screen()->setReflowLines(enableReflowLines);
0283             view->screenWindow()->screen()->setIgnoreWcWidth(ignoreWcWidth);
0284             view->screenWindow()->screen()->setEnableUrlExtractor(shouldEnableUrlExtractor);
0285             if (shouldEnableUrlExtractor) {
0286                 view->screenWindow()->screen()->urlExtractor()->setAllowedLinkSchema(profile->escapedLinksSchema());
0287                 connect(session->emulation(),
0288                         &Emulation::toggleUrlExtractionRequest,
0289                         view->screenWindow()->screen()->urlExtractor(),
0290                         &EscapeSequenceUrlExtractor::toggleUrlInput);
0291             }
0292         }
0293     }
0294 }
0295 
0296 void SessionManager::sessionProfileCommandReceived(Session *session, const QString &text)
0297 {
0298     Q_ASSERT(session);
0299 
0300     // store the font for each view if zoom was applied so that they can
0301     // be restored after applying the new profile
0302     struct ZoomFontInfo {
0303         TerminalDisplay *display = nullptr;
0304         QFont font;
0305     };
0306     std::vector<ZoomFontInfo> zoomFontSizes;
0307 
0308     const QList<TerminalDisplay *> viewsList = session->views();
0309 
0310     for (TerminalDisplay *view : viewsList) {
0311         const QFont viewCurFont = view->terminalFont()->getVTFont();
0312         if (viewCurFont != _sessionProfiles[session]->font()) {
0313             zoomFontSizes.push_back({view, viewCurFont});
0314         }
0315     }
0316 
0317     Profile::Ptr newProfile;
0318     if (!_sessionRuntimeProfiles.contains(session)) {
0319         newProfile = new Profile(_sessionProfiles[session]);
0320         _sessionRuntimeProfiles.insert(session, newProfile);
0321     } else {
0322         newProfile = _sessionRuntimeProfiles[session];
0323     }
0324 
0325     ProfileCommandParser parser;
0326     newProfile->assignProperties(parser.parse(text));
0327 
0328     _sessionProfiles[session] = newProfile;
0329     applyProfile(newProfile, true);
0330     Q_EMIT sessionUpdated(session);
0331 
0332     for (auto &[view, font] : zoomFontSizes) {
0333         view->terminalFont()->setVTFont(font);
0334     }
0335 }
0336 
0337 void SessionManager::saveSessions(KConfig *config)
0338 {
0339     // The session IDs can't be restored.
0340     // So we need to map the old ID to the future new ID.
0341     int n = 1;
0342     _restoreMapping.clear();
0343 
0344     for (Session *session : std::as_const(_sessions)) {
0345         QString name = QLatin1String("Session") + QString::number(n);
0346         KConfigGroup group(config, name);
0347 
0348         group.writePathEntry("Profile", _sessionProfiles.value(session)->path());
0349         session->saveSession(group);
0350         _restoreMapping.insert(session, n);
0351         n++;
0352     }
0353 
0354     KConfigGroup group(config, QStringLiteral("Number"));
0355     group.writeEntry("NumberOfSessions", _sessions.count());
0356 }
0357 
0358 int SessionManager::getRestoreId(Session *session)
0359 {
0360     return _restoreMapping.value(session);
0361 }
0362 
0363 void SessionManager::restoreSessions(KConfig *config)
0364 {
0365     KConfigGroup group(config, QStringLiteral("Number"));
0366     const int sessions = group.readEntry("NumberOfSessions", 0);
0367 
0368     // Any sessions saved?
0369     for (int n = 1; n <= sessions; n++) {
0370         const QString name = QLatin1String("Session") + QString::number(n);
0371         KConfigGroup sessionGroup(config, name);
0372 
0373         const QString profile = sessionGroup.readPathEntry("Profile", QString());
0374         Profile::Ptr ptr = ProfileManager::instance()->defaultProfile();
0375         if (!profile.isEmpty()) {
0376             ptr = ProfileManager::instance()->loadProfile(profile);
0377         }
0378         Session *session = createSession(ptr);
0379         session->restoreSession(sessionGroup);
0380     }
0381 }
0382 
0383 Session *SessionManager::idToSession(int id)
0384 {
0385     for (Session *session : std::as_const(_sessions)) {
0386         if (session->sessionId() == id) {
0387             return session;
0388         }
0389     }
0390     // this should not happen
0391     qCDebug(KonsoleDebug) << "Failed to find session for ID" << id;
0392     return nullptr;
0393 }
0394 
0395 #include "moc_SessionManager.cpp"