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"