File indexing completed on 2024-05-05 05:56:59

0001 /*
0002   SPDX-FileCopyrightText: 2008-2014 Eike Hein <hein@kde.org>
0003   SPDX-FileCopyrightText: 2009 Juan Carlos Torres <carlosdgtorres@gmail.com>
0004 
0005   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "session.h"
0009 #include "terminal.h"
0010 
0011 #include <algorithm>
0012 
0013 int Session::m_availableSessionId = 0;
0014 
0015 Session::Session(const QString &workingDir, SessionType type, QWidget *parent)
0016     : QObject(parent)
0017 {
0018     m_workingDir = workingDir;
0019     m_sessionId = m_availableSessionId;
0020     m_availableSessionId++;
0021 
0022     m_activeTerminalId = -1;
0023 
0024     m_closable = true;
0025 
0026     m_baseSplitter = new Splitter(Qt::Horizontal, parent);
0027     connect(m_baseSplitter, SIGNAL(destroyed()), this, SLOT(prepareShutdown()));
0028 
0029     setupSession(type);
0030 }
0031 
0032 Session::~Session()
0033 {
0034     if (m_baseSplitter)
0035         delete m_baseSplitter;
0036 
0037     Q_EMIT destroyed(m_sessionId);
0038 }
0039 
0040 void Session::setupSession(SessionType type)
0041 {
0042     switch (type) {
0043     case Single: {
0044         Terminal *terminal = addTerminal(m_baseSplitter);
0045         setActiveTerminal(terminal->id());
0046 
0047         break;
0048     }
0049 
0050     case TwoHorizontal: {
0051         int splitterWidth = m_baseSplitter->width();
0052 
0053         Terminal *terminal = addTerminal(m_baseSplitter);
0054         addTerminal(m_baseSplitter);
0055 
0056         QList<int> newSplitterSizes;
0057         newSplitterSizes << (splitterWidth / 2) << (splitterWidth / 2);
0058         m_baseSplitter->setSizes(newSplitterSizes);
0059 
0060         QWidget *terminalWidget = terminal->terminalWidget();
0061 
0062         if (terminalWidget) {
0063             terminalWidget->setFocus();
0064             setActiveTerminal(terminal->id());
0065         }
0066 
0067         break;
0068     }
0069 
0070     case TwoVertical: {
0071         m_baseSplitter->setOrientation(Qt::Vertical);
0072 
0073         int splitterHeight = m_baseSplitter->height();
0074 
0075         Terminal *terminal = addTerminal(m_baseSplitter);
0076         addTerminal(m_baseSplitter);
0077 
0078         QList<int> newSplitterSizes;
0079         newSplitterSizes << (splitterHeight / 2) << (splitterHeight / 2);
0080         m_baseSplitter->setSizes(newSplitterSizes);
0081 
0082         QWidget *terminalWidget = terminal->terminalWidget();
0083 
0084         if (terminalWidget) {
0085             terminalWidget->setFocus();
0086             setActiveTerminal(terminal->id());
0087         }
0088 
0089         break;
0090     }
0091 
0092     case Quad: {
0093         int splitterWidth = m_baseSplitter->width();
0094         int splitterHeight = m_baseSplitter->height();
0095 
0096         m_baseSplitter->setOrientation(Qt::Vertical);
0097 
0098         Splitter *upperSplitter = new Splitter(Qt::Horizontal, m_baseSplitter);
0099         connect(upperSplitter, SIGNAL(destroyed()), this, SLOT(cleanup()));
0100 
0101         Splitter *lowerSplitter = new Splitter(Qt::Horizontal, m_baseSplitter);
0102         connect(lowerSplitter, SIGNAL(destroyed()), this, SLOT(cleanup()));
0103 
0104         Terminal *terminal = addTerminal(upperSplitter);
0105         addTerminal(upperSplitter);
0106 
0107         addTerminal(lowerSplitter);
0108         addTerminal(lowerSplitter);
0109 
0110         QList<int> newSplitterSizes;
0111         newSplitterSizes << (splitterHeight / 2) << (splitterHeight / 2);
0112         m_baseSplitter->setSizes(newSplitterSizes);
0113 
0114         newSplitterSizes.clear();
0115         newSplitterSizes << (splitterWidth / 2) << (splitterWidth / 2);
0116         upperSplitter->setSizes(newSplitterSizes);
0117         lowerSplitter->setSizes(newSplitterSizes);
0118 
0119         QWidget *terminalWidget = terminal->terminalWidget();
0120 
0121         if (terminalWidget) {
0122             terminalWidget->setFocus();
0123             setActiveTerminal(terminal->id());
0124         }
0125 
0126         break;
0127     }
0128 
0129     default: {
0130         addTerminal(m_baseSplitter);
0131 
0132         break;
0133     }
0134     }
0135 }
0136 
0137 Terminal *Session::addTerminal(QSplitter *parent, QString workingDir)
0138 {
0139     if (workingDir.isEmpty()) {
0140         // fallback to session's default working dir
0141         workingDir = m_workingDir;
0142     }
0143 
0144     std::unique_ptr<Terminal> terminal = std::make_unique<Terminal>(workingDir, parent);
0145     connect(terminal.get(), SIGNAL(activated(int)), this, SLOT(setActiveTerminal(int)));
0146     connect(terminal.get(), SIGNAL(manuallyActivated(Terminal *)), this, SIGNAL(terminalManuallyActivated(Terminal *)));
0147     connect(terminal.get(), SIGNAL(titleChanged(int, QString)), this, SLOT(setTitle(int, QString)));
0148     connect(terminal.get(), SIGNAL(keyboardInputBlocked(Terminal *)), this, SIGNAL(keyboardInputBlocked(Terminal *)));
0149     connect(terminal.get(), SIGNAL(silenceDetected(Terminal *)), this, SIGNAL(silenceDetected(Terminal *)));
0150     connect(terminal.get(), &Terminal::closeRequested, this, QOverload<int>::of(&Session::cleanup));
0151 
0152     Terminal *term = terminal.get();
0153 
0154     m_terminals[terminal->id()] = std::move(terminal);
0155 
0156     Q_EMIT wantsBlurChanged();
0157 
0158     QWidget *terminalWidget = term->terminalWidget();
0159     if (terminalWidget)
0160         terminalWidget->setFocus();
0161 
0162     parent->addWidget(term->partWidget());
0163 
0164     return term;
0165 }
0166 
0167 void Session::closeTerminal(int terminalId)
0168 {
0169     if (terminalId == -1)
0170         terminalId = m_activeTerminalId;
0171     if (terminalId == -1)
0172         return;
0173     if (!m_terminals.contains(terminalId))
0174         return;
0175 
0176     cleanup(terminalId);
0177 }
0178 
0179 void Session::focusPreviousTerminal()
0180 {
0181     if (m_activeTerminalId == -1)
0182         return;
0183     if (!m_terminals.contains(m_activeTerminalId))
0184         return;
0185 
0186     std::map<int, std::unique_ptr<Terminal>>::iterator currentTerminal = m_terminals.find(m_activeTerminalId);
0187 
0188     std::map<int, std::unique_ptr<Terminal>>::iterator previousTerminal;
0189 
0190     if (currentTerminal == m_terminals.begin()) {
0191         previousTerminal = std::prev(m_terminals.end());
0192     } else {
0193         previousTerminal = std::prev(currentTerminal);
0194     }
0195 
0196     QWidget *terminalWidget = previousTerminal->second->terminalWidget();
0197     if (terminalWidget) {
0198         terminalWidget->setFocus();
0199     }
0200 }
0201 
0202 void Session::focusNextTerminal()
0203 {
0204     if (m_activeTerminalId == -1)
0205         return;
0206     if (!m_terminals.contains(m_activeTerminalId))
0207         return;
0208 
0209     std::map<int, std::unique_ptr<Terminal>>::iterator currentTerminal = m_terminals.find(m_activeTerminalId);
0210 
0211     std::map<int, std::unique_ptr<Terminal>>::iterator nextTerminal = std::next(currentTerminal);
0212 
0213     if (nextTerminal == m_terminals.end()) {
0214         nextTerminal = m_terminals.begin();
0215     }
0216 
0217     QWidget *terminalWidget = nextTerminal->second->terminalWidget();
0218     if (terminalWidget) {
0219         terminalWidget->setFocus();
0220     }
0221 }
0222 
0223 int Session::splitLeftRight(int terminalId)
0224 {
0225     if (terminalId == -1)
0226         terminalId = m_activeTerminalId;
0227     if (terminalId == -1)
0228         return -1;
0229     if (!m_terminals.contains(terminalId))
0230         return -1;
0231 
0232     Terminal *terminal = m_terminals[terminalId].get();
0233 
0234     if (terminal)
0235         return split(terminal, Qt::Horizontal);
0236     else
0237         return -1;
0238 }
0239 
0240 int Session::splitTopBottom(int terminalId)
0241 {
0242     if (terminalId == -1)
0243         terminalId = m_activeTerminalId;
0244     if (terminalId == -1)
0245         return -1;
0246     if (!m_terminals.contains(terminalId))
0247         return -1;
0248 
0249     Terminal *terminal = m_terminals[terminalId].get();
0250 
0251     if (terminal)
0252         return split(terminal, Qt::Vertical);
0253     else
0254         return -1;
0255 }
0256 
0257 int Session::split(Terminal *terminal, Qt::Orientation orientation)
0258 {
0259     Splitter *splitter = static_cast<Splitter *>(terminal->splitter());
0260 
0261     if (splitter->count() == 1) {
0262         int splitterWidth = splitter->width();
0263 
0264         if (splitter->orientation() != orientation)
0265             splitter->setOrientation(orientation);
0266 
0267         terminal = addTerminal(splitter, terminal->currentWorkingDirectory());
0268 
0269         QList<int> newSplitterSizes;
0270         newSplitterSizes << (splitterWidth / 2) << (splitterWidth / 2);
0271         splitter->setSizes(newSplitterSizes);
0272 
0273         QWidget *partWidget = terminal->partWidget();
0274         if (partWidget)
0275             partWidget->show();
0276 
0277         m_activeTerminalId = terminal->id();
0278     } else {
0279         QList<int> splitterSizes = splitter->sizes();
0280 
0281         Splitter *newSplitter = new Splitter(orientation, splitter);
0282         connect(newSplitter, SIGNAL(destroyed()), this, SLOT(cleanup()));
0283 
0284         if (splitter->indexOf(terminal->partWidget()) == 0)
0285             splitter->insertWidget(0, newSplitter);
0286 
0287         QWidget *partWidget = terminal->partWidget();
0288         if (partWidget)
0289             partWidget->setParent(newSplitter);
0290 
0291         terminal->setSplitter(newSplitter);
0292 
0293         terminal = addTerminal(newSplitter, terminal->currentWorkingDirectory());
0294 
0295         splitter->setSizes(splitterSizes);
0296         QList<int> newSplitterSizes;
0297         newSplitterSizes << (splitterSizes[1] / 2) << (splitterSizes[1] / 2);
0298         newSplitter->setSizes(newSplitterSizes);
0299 
0300         newSplitter->show();
0301 
0302         partWidget = terminal->partWidget();
0303         if (partWidget)
0304             partWidget->show();
0305 
0306         m_activeTerminalId = terminal->id();
0307     }
0308 
0309     return m_activeTerminalId;
0310 }
0311 
0312 int Session::tryGrowTerminal(int terminalId, GrowthDirection direction, uint pixels)
0313 {
0314     Terminal *terminal = getTerminal(terminalId);
0315     Splitter *splitter = static_cast<Splitter *>(terminal->splitter());
0316     QWidget *child = terminal->partWidget();
0317 
0318     while (splitter) {
0319         bool isHorizontal = (direction == Right || direction == Left);
0320         bool isForward = (direction == Down || direction == Right);
0321 
0322         // Detecting correct orientation.
0323         if ((splitter->orientation() == Qt::Horizontal && isHorizontal) || (splitter->orientation() == Qt::Vertical && !isHorizontal)) {
0324             int currentPos = splitter->indexOf(child);
0325 
0326             if (currentPos != -1 // Next/Prev movable element detection.
0327                 && (currentPos != 0 || isForward) && (currentPos != splitter->count() - 1 || !isForward)) {
0328                 QList<int> currentSizes = splitter->sizes();
0329                 int oldSize = currentSizes[currentPos];
0330 
0331                 int affected = isForward ? currentPos + 1 : currentPos - 1;
0332                 currentSizes[currentPos] += pixels;
0333                 currentSizes[affected] -= pixels;
0334                 splitter->setSizes(currentSizes);
0335 
0336                 return splitter->sizes().at(currentPos) - oldSize;
0337             }
0338         }
0339         // Try with a higher level.
0340         child = splitter;
0341         splitter = static_cast<Splitter *>(splitter->parentWidget());
0342     }
0343 
0344     return -1;
0345 }
0346 
0347 void Session::setActiveTerminal(int terminalId)
0348 {
0349     m_activeTerminalId = terminalId;
0350 
0351     setTitle(m_activeTerminalId, m_terminals[m_activeTerminalId]->title());
0352 }
0353 
0354 void Session::setTitle(int terminalId, const QString &title)
0355 {
0356     if (terminalId == m_activeTerminalId) {
0357         m_title = title;
0358 
0359         Q_EMIT titleChanged(m_title);
0360         Q_EMIT titleChanged(m_sessionId, m_title);
0361     }
0362 }
0363 
0364 void Session::cleanup(int terminalId)
0365 {
0366     if (m_activeTerminalId == terminalId && m_terminals.size() > 1)
0367         focusPreviousTerminal();
0368 
0369     m_terminals.erase(terminalId);
0370     Q_EMIT wantsBlurChanged();
0371 
0372     cleanup();
0373 }
0374 
0375 void Session::cleanup()
0376 {
0377     if (!m_baseSplitter)
0378         return;
0379 
0380     m_baseSplitter->recursiveCleanup();
0381 
0382     if (m_terminals.empty())
0383         m_baseSplitter->deleteLater();
0384 }
0385 
0386 void Session::prepareShutdown()
0387 {
0388     m_baseSplitter = nullptr;
0389 
0390     deleteLater();
0391 }
0392 
0393 const QString Session::terminalIdList()
0394 {
0395     QStringList idList;
0396     for (auto &[id, terminal] : m_terminals) {
0397         idList << QString::number(id);
0398     }
0399 
0400     return idList.join(QLatin1Char(','));
0401 }
0402 
0403 bool Session::hasTerminal(int terminalId)
0404 {
0405     return m_terminals.contains(terminalId);
0406 }
0407 
0408 Terminal *Session::getTerminal(int terminalId)
0409 {
0410     if (!m_terminals.contains(terminalId))
0411         return nullptr;
0412 
0413     return m_terminals[terminalId].get();
0414 }
0415 
0416 void Session::runCommand(const QString &command, int terminalId)
0417 {
0418     if (terminalId == -1)
0419         terminalId = m_activeTerminalId;
0420     if (terminalId == -1)
0421         return;
0422     if (!m_terminals.contains(terminalId))
0423         return;
0424 
0425     m_terminals[terminalId]->runCommand(command);
0426 }
0427 
0428 void Session::manageProfiles()
0429 {
0430     if (m_activeTerminalId == -1)
0431         return;
0432     if (!m_terminals.contains(m_activeTerminalId))
0433         return;
0434 
0435     m_terminals[m_activeTerminalId]->manageProfiles();
0436 }
0437 
0438 void Session::editProfile()
0439 {
0440     if (m_activeTerminalId == -1)
0441         return;
0442     if (!m_terminals.contains(m_activeTerminalId))
0443         return;
0444 
0445     m_terminals[m_activeTerminalId]->editProfile();
0446 }
0447 
0448 bool Session::keyboardInputEnabled()
0449 {
0450     return std::all_of(m_terminals.cbegin(), m_terminals.cend(), [](auto &it) {
0451         auto &[id, terminal] = it;
0452         return terminal->keyboardInputEnabled();
0453     });
0454 }
0455 
0456 void Session::setKeyboardInputEnabled(bool enabled)
0457 {
0458     for (auto &[id, terminal] : m_terminals) {
0459         terminal->setKeyboardInputEnabled(enabled);
0460     }
0461 }
0462 
0463 bool Session::keyboardInputEnabled(int terminalId)
0464 {
0465     if (!m_terminals.contains(terminalId))
0466         return false;
0467 
0468     return m_terminals[terminalId]->keyboardInputEnabled();
0469 }
0470 
0471 void Session::setKeyboardInputEnabled(int terminalId, bool enabled)
0472 {
0473     if (!m_terminals.contains(terminalId))
0474         return;
0475 
0476     m_terminals[terminalId]->setKeyboardInputEnabled(enabled);
0477 }
0478 
0479 bool Session::hasTerminalsWithKeyboardInputEnabled()
0480 {
0481     return std::any_of(m_terminals.cbegin(), m_terminals.cend(), [](auto &it) {
0482         auto &[id, terminal] = it;
0483         return terminal->keyboardInputEnabled();
0484     });
0485 }
0486 
0487 bool Session::hasTerminalsWithKeyboardInputDisabled()
0488 {
0489     return std::any_of(m_terminals.cbegin(), m_terminals.cend(), [](auto &it) {
0490         auto &[id, terminal] = it;
0491         return !terminal->keyboardInputEnabled();
0492     });
0493 }
0494 
0495 bool Session::monitorActivityEnabled()
0496 {
0497     return std::all_of(m_terminals.cbegin(), m_terminals.cend(), [](auto &it) {
0498         auto &[id, terminal] = it;
0499         return terminal->monitorActivityEnabled();
0500     });
0501 }
0502 
0503 void Session::setMonitorActivityEnabled(bool enabled)
0504 {
0505     for (auto &[id, terminal] : m_terminals) {
0506         setMonitorActivityEnabled(id, enabled);
0507     }
0508 }
0509 
0510 bool Session::monitorActivityEnabled(int terminalId)
0511 {
0512     if (!m_terminals.contains(terminalId))
0513         return false;
0514 
0515     return m_terminals[terminalId]->monitorActivityEnabled();
0516 }
0517 
0518 void Session::setMonitorActivityEnabled(int terminalId, bool enabled)
0519 {
0520     if (!m_terminals.contains(terminalId))
0521         return;
0522 
0523     Terminal *terminal = m_terminals[terminalId].get();
0524 
0525     connect(terminal, SIGNAL(activityDetected(Terminal *)), this, SIGNAL(activityDetected(Terminal *)), Qt::UniqueConnection);
0526 
0527     terminal->setMonitorActivityEnabled(enabled);
0528 }
0529 
0530 bool Session::hasTerminalsWithMonitorActivityEnabled()
0531 {
0532     return std::any_of(m_terminals.cbegin(), m_terminals.cend(), [](auto &it) {
0533         auto &[id, terminal] = it;
0534         return terminal->monitorActivityEnabled();
0535     });
0536 }
0537 
0538 bool Session::hasTerminalsWithMonitorActivityDisabled()
0539 {
0540     return std::any_of(m_terminals.cbegin(), m_terminals.cend(), [](auto &it) {
0541         auto &[id, terminal] = it;
0542         return !terminal->monitorActivityEnabled();
0543     });
0544 }
0545 
0546 void Session::reconnectMonitorActivitySignals()
0547 {
0548     for (auto &[id, terminal] : m_terminals) {
0549         // clang-format off
0550         connect(terminal.get(), SIGNAL(activityDetected(Terminal*)), this, SIGNAL(activityDetected(Terminal*)), Qt::UniqueConnection);
0551         // clang-format on
0552     }
0553 }
0554 
0555 bool Session::monitorSilenceEnabled()
0556 {
0557     return std::all_of(m_terminals.cbegin(), m_terminals.cend(), [](auto &it) {
0558         auto &[id, terminal] = it;
0559         return terminal->monitorSilenceEnabled();
0560     });
0561 }
0562 
0563 void Session::setMonitorSilenceEnabled(bool enabled)
0564 {
0565     for (auto &[id, terminal] : m_terminals) {
0566         terminal->setMonitorSilenceEnabled(enabled);
0567     }
0568 }
0569 
0570 bool Session::monitorSilenceEnabled(int terminalId)
0571 {
0572     if (!m_terminals.contains(terminalId))
0573         return false;
0574 
0575     return m_terminals[terminalId]->monitorSilenceEnabled();
0576 }
0577 
0578 void Session::setMonitorSilenceEnabled(int terminalId, bool enabled)
0579 {
0580     if (!m_terminals.contains(terminalId))
0581         return;
0582 
0583     m_terminals[terminalId]->setMonitorSilenceEnabled(enabled);
0584 }
0585 
0586 bool Session::hasTerminalsWithMonitorSilenceDisabled()
0587 {
0588     return std::any_of(m_terminals.cbegin(), m_terminals.cend(), [](auto &it) {
0589         auto &[id, terminal] = it;
0590         return !terminal->monitorSilenceEnabled();
0591     });
0592 }
0593 
0594 bool Session::hasTerminalsWithMonitorSilenceEnabled()
0595 {
0596     return std::any_of(m_terminals.cbegin(), m_terminals.cend(), [](auto &it) {
0597         auto &[id, terminal] = it;
0598         return terminal->monitorSilenceEnabled();
0599     });
0600 }
0601 
0602 bool Session::wantsBlur() const
0603 {
0604     return std::any_of(m_terminals.cbegin(), m_terminals.cend(), [](auto &it) {
0605         auto &[id, terminal] = it;
0606         return terminal->wantsBlur();
0607     });
0608 }
0609 
0610 #include "moc_session.cpp"