1 /*  Copyright (C) 2008 e_k (e_k@users.sourceforge.net)
2 
3     This library is free software; you can redistribute it and/or
4     modify it under the terms of the GNU Library General Public
5     License as published by the Free Software Foundation; either
6     version 2 of the License, or (at your option) any later version.
7 
8     This library is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11     Library General Public License for more details.
12 
13     You should have received a copy of the GNU Library General Public License
14     along with this library; see the file COPYING.LIB.  If not, write to
15     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16     Boston, MA 02110-1301, USA.
17 */
18 
19 #include <QLayout>
20 #include <QBoxLayout>
21 #include <QtDebug>
22 #include <QDir>
23 #include <QMessageBox>
24 
25 #include "ColorTables.h"
26 #include "Session.h"
27 #include "Screen.h"
28 #include "ScreenWindow.h"
29 #include "Emulation.h"
30 #include "TerminalDisplay.h"
31 #include "KeyboardTranslator.h"
32 #include "ColorScheme.h"
33 #include "SearchBar.h"
34 #include "qtermwidget.h"
35 
36 
37 #define STEP_ZOOM 1
38 
39 using namespace Konsole;
40 
createTermWidget(int startnow,void * parent)41 void *createTermWidget(int startnow, void *parent)
42 {
43     return (void*) new QTermWidget(startnow, (QWidget*)parent);
44 }
45 
46 struct TermWidgetImpl {
47     TermWidgetImpl(QWidget* parent = 0);
48 
49     TerminalDisplay *m_terminalDisplay;
50     Session *m_session;
51 
52     Session* createSession(QWidget* parent);
53     TerminalDisplay* createTerminalDisplay(Session *session, QWidget* parent);
54 };
55 
TermWidgetImpl(QWidget * parent)56 TermWidgetImpl::TermWidgetImpl(QWidget* parent)
57 {
58     this->m_session = createSession(parent);
59     this->m_terminalDisplay = createTerminalDisplay(this->m_session, parent);
60 }
61 
62 
createSession(QWidget * parent)63 Session *TermWidgetImpl::createSession(QWidget* parent)
64 {
65     Session *session = new Session(parent);
66 
67     session->setTitle(Session::NameRole, QStringLiteral("QTermWidget"));
68 
69     /* Thats a freaking bad idea!!!!
70      * /bin/bash is not there on every system
71      * better set it to the current $SHELL
72      * Maybe you can also make a list available and then let the widget-owner decide what to use.
73      * By setting it to $SHELL right away we actually make the first filecheck obsolete.
74      * But as iam not sure if you want to do anything else ill just let both checks in and set this to $SHELL anyway.
75      */
76     //session->setProgram("/bin/bash");
77 
78     session->setProgram(getenv("SHELL"));
79 
80 
81 
82     QStringList args(QLatin1String(""));
83     session->setArguments(args);
84     session->setAutoClose(true);
85 
86     session->setCodec(QTextCodec::codecForName("UTF-8"));
87 
88     session->setFlowControlEnabled(true);
89     session->setHistoryType(HistoryTypeBuffer(1000));
90 
91     session->setDarkBackground(true);
92 
93     session->setKeyBindings(QLatin1String(""));
94     return session;
95 }
96 
createTerminalDisplay(Session * session,QWidget * parent)97 TerminalDisplay *TermWidgetImpl::createTerminalDisplay(Session *session, QWidget* parent)
98 {
99 //    TerminalDisplay* display = new TerminalDisplay(this);
100     TerminalDisplay* display = new TerminalDisplay(parent);
101 
102     display->setBellMode(TerminalDisplay::NotifyBell);
103     display->setTerminalSizeHint(true);
104     display->setTripleClickMode(TerminalDisplay::SelectWholeLine);
105     display->setTerminalSizeStartup(true);
106 
107     display->setRandomSeed(session->sessionId() * 31);
108 
109     return display;
110 }
111 
112 
QTermWidget(int startnow,QWidget * parent)113 QTermWidget::QTermWidget(int startnow, QWidget *parent)
114     : QWidget(parent)
115 {
116     init(startnow);
117 }
118 
QTermWidget(QWidget * parent)119 QTermWidget::QTermWidget(QWidget *parent)
120     : QWidget(parent)
121 {
122     init(1);
123 }
124 
selectionChanged(bool textSelected)125 void QTermWidget::selectionChanged(bool textSelected)
126 {
127     emit copyAvailable(textSelected);
128 }
129 
find()130 void QTermWidget::find()
131 {
132     search(true, false);
133 }
134 
findNext()135 void QTermWidget::findNext()
136 {
137     search(true, true);
138 }
139 
findPrevious()140 void QTermWidget::findPrevious()
141 {
142     search(false, false);
143 }
144 
search(bool forwards,bool next)145 void QTermWidget::search(bool forwards, bool next)
146 {
147     int startColumn, startLine;
148 
149     if (next) // search from just after current selection
150     {
151         m_impl->m_terminalDisplay->screenWindow()->screen()->getSelectionEnd(startColumn, startLine);
152         startColumn++;
153     }
154     else // search from start of current selection
155     {
156         m_impl->m_terminalDisplay->screenWindow()->screen()->getSelectionStart(startColumn, startLine);
157     }
158 
159     qDebug() << "current selection starts at: " << startColumn << startLine;
160     qDebug() << "current cursor position: " << m_impl->m_terminalDisplay->screenWindow()->cursorPosition();
161 
162     QRegExp regExp(m_searchBar->searchText());
163     regExp.setPatternSyntax(m_searchBar->useRegularExpression() ? QRegExp::RegExp : QRegExp::FixedString);
164     regExp.setCaseSensitivity(m_searchBar->matchCase() ? Qt::CaseSensitive : Qt::CaseInsensitive);
165 
166     HistorySearch *historySearch =
167             new HistorySearch(m_impl->m_session->emulation(), regExp, forwards, startColumn, startLine, this);
168     connect(historySearch, &HistorySearch::matchFound, this, &QTermWidget::matchFound);
169     connect(historySearch, &HistorySearch::noMatchFound, this, &QTermWidget::noMatchFound);
170     connect(historySearch, &HistorySearch::noMatchFound, m_searchBar, &SearchBar::noMatchFound);
171     historySearch->search();
172 }
173 
174 
matchFound(int startColumn,int startLine,int endColumn,int endLine)175 void QTermWidget::matchFound(int startColumn, int startLine, int endColumn, int endLine)
176 {
177     ScreenWindow* sw = m_impl->m_terminalDisplay->screenWindow();
178     qDebug() << "Scroll to" << startLine;
179     sw->scrollTo(startLine);
180     sw->setTrackOutput(false);
181     sw->notifyOutputChanged();
182     sw->setSelectionStart(startColumn, startLine - sw->currentLine(), false);
183     sw->setSelectionEnd(endColumn, endLine - sw->currentLine());
184 }
185 
noMatchFound()186 void QTermWidget::noMatchFound()
187 {
188         m_impl->m_terminalDisplay->screenWindow()->clearSelection();
189 }
190 
getShellPID()191 int QTermWidget::getShellPID()
192 {
193     return m_impl->m_session->processId();
194 }
195 
changeDir(const QString & dir)196 void QTermWidget::changeDir(const QString & dir)
197 {
198     /*
199        this is a very hackish way of trying to determine if the shell is in
200        the foreground before attempting to change the directory.  It may not
201        be portable to anything other than Linux.
202     */
203     QString strCmd;
204     strCmd.setNum(getShellPID());
205     strCmd.prepend("ps -j ");
206     strCmd.append(" | tail -1 | awk '{ print $5 }' | grep -q \\+");
207     int retval = system(strCmd.toStdString().c_str());
208 
209     if (!retval) {
210         QString cmd = "cd " + dir + "\n";
211         sendText(cmd);
212     }
213 }
214 
sizeHint() const215 QSize QTermWidget::sizeHint() const
216 {
217     QSize size = m_impl->m_terminalDisplay->sizeHint();
218     size.rheight() = 150;
219     return size;
220 }
221 
startShellProgram()222 void QTermWidget::startShellProgram()
223 {
224     if ( m_impl->m_session->isRunning() ) {
225         return;
226     }
227 
228     m_impl->m_session->run();
229 }
230 
startTerminalTeletype()231 void QTermWidget::startTerminalTeletype()
232 {
233     if ( m_impl->m_session->isRunning() ) {
234         return;
235     }
236 
237     m_impl->m_session->runEmptyPTY();
238     // redirect data from TTY to external recipient
239     connect( m_impl->m_session->emulation(), &Emulation::sendData,
240              this, &QTermWidget::sendData );
241 }
242 
init(int startnow)243 void QTermWidget::init(int startnow)
244 {
245     m_layout = new QVBoxLayout();
246     m_layout->setMargin(0);
247     setLayout(m_layout);
248 
249     m_impl = new TermWidgetImpl(this);
250     m_impl->m_terminalDisplay->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
251     m_layout->addWidget(m_impl->m_terminalDisplay);
252 
253     connect(m_impl->m_session, &Session::bellRequest, m_impl->m_terminalDisplay, &TerminalDisplay::bell);
254     connect(m_impl->m_terminalDisplay, &TerminalDisplay::notifyBell, this, &QTermWidget::bell);
255 
256     connect(m_impl->m_session, &Session::activity, this, &QTermWidget::activity);
257     connect(m_impl->m_session, &Session::silence, this, &QTermWidget::silence);
258 
259     // That's OK, FilterChain's dtor takes care of UrlFilter.
260     UrlFilter *urlFilter = new UrlFilter();
261     connect(urlFilter, &UrlFilter::activated, this, &QTermWidget::urlActivated);
262     m_impl->m_terminalDisplay->filterChain()->addFilter(urlFilter);
263 
264     m_searchBar = new SearchBar(this);
265     m_searchBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
266     connect(m_searchBar, &SearchBar::searchCriteriaChanged, this, &QTermWidget::find);
267     connect(m_searchBar, &SearchBar::findNext, this, &QTermWidget::findNext);
268     connect(m_searchBar, &SearchBar::findPrevious, this, &QTermWidget::findPrevious);
269     m_layout->addWidget(m_searchBar);
270     m_searchBar->hide();
271 
272     if (startnow && m_impl->m_session) {
273         m_impl->m_session->run();
274     }
275 
276     this->setFocus( Qt::OtherFocusReason );
277     this->setFocusPolicy( Qt::WheelFocus );
278     m_impl->m_terminalDisplay->resize(this->size());
279 
280     this->setFocusProxy(m_impl->m_terminalDisplay);
281     connect(m_impl->m_terminalDisplay, &TerminalDisplay::copyAvailable,
282             this, &QTermWidget::selectionChanged);
283     connect(m_impl->m_terminalDisplay, &TerminalDisplay::termGetFocus,
284             this, &QTermWidget::termGetFocus);
285     connect(m_impl->m_terminalDisplay, &TerminalDisplay::termLostFocus,
286             this, &QTermWidget::termLostFocus);
287     connect(m_impl->m_terminalDisplay, &TerminalDisplay::keyPressedSignal,
288             this, &QTermWidget::termKeyPressed);
289 //    m_impl->m_terminalDisplay->setSize(80, 40);
290 
291     QFont font = QApplication::font();
292     font.setFamily(QStringLiteral("Monospace"));
293     font.setPointSize(10);
294     font.setStyleHint(QFont::TypeWriter);
295     setTerminalFont(font);
296     m_searchBar->setFont(font);
297 
298     setScrollBarPosition(NoScrollBar);
299 
300     m_impl->m_session->addView(m_impl->m_terminalDisplay);
301 
302     connect(m_impl->m_session, &Session::finished, this, &QTermWidget::sessionFinished);
303 }
304 
305 
~QTermWidget()306 QTermWidget::~QTermWidget()
307 {
308     delete m_impl;
309     emit destroyed();
310 }
311 
312 
setTerminalFont(const QFont & font)313 void QTermWidget::setTerminalFont(const QFont &font)
314 {
315     if (!m_impl->m_terminalDisplay)
316         return;
317     m_impl->m_terminalDisplay->setVTFont(font);
318 }
319 
getTerminalFont()320 QFont QTermWidget::getTerminalFont()
321 {
322     if (!m_impl->m_terminalDisplay)
323         return QFont();
324     return m_impl->m_terminalDisplay->getVTFont();
325 }
326 
setTerminalOpacity(qreal level)327 void QTermWidget::setTerminalOpacity(qreal level)
328 {
329     if (!m_impl->m_terminalDisplay)
330         return;
331 
332     m_impl->m_terminalDisplay->setOpacity(level);
333 }
334 
setShellProgram(const QString & progname)335 void QTermWidget::setShellProgram(const QString &progname)
336 {
337     if (!m_impl->m_session)
338         return;
339     m_impl->m_session->setProgram(progname);
340 }
341 
setWorkingDirectory(const QString & dir)342 void QTermWidget::setWorkingDirectory(const QString& dir)
343 {
344     if (!m_impl->m_session)
345         return;
346     m_impl->m_session->setInitialWorkingDirectory(dir);
347 }
348 
workingDirectory()349 QString QTermWidget::workingDirectory()
350 {
351     if (!m_impl->m_session)
352         return QString();
353 
354 #ifdef Q_OS_LINUX
355     // Christian Surlykke: On linux we could look at /proc/<pid>/cwd which should be a link to current
356     // working directory (<pid>: process id of the shell). I don't know about BSD.
357     // Maybe we could just offer it when running linux, for a start.
358     QDir d(QStringLiteral("/proc/%1/cwd").arg(getShellPID()));
359     if (!d.exists())
360     {
361         qDebug() << "Cannot find" << d.dirName();
362         goto fallback;
363     }
364     return d.canonicalPath();
365 #endif
366 
367 fallback:
368     // fallback, initial WD
369     return m_impl->m_session->initialWorkingDirectory();
370 }
371 
setArgs(const QStringList & args)372 void QTermWidget::setArgs(const QStringList &args)
373 {
374     if (!m_impl->m_session)
375         return;
376     m_impl->m_session->setArguments(args);
377 }
378 
setTextCodec(QTextCodec * codec)379 void QTermWidget::setTextCodec(QTextCodec *codec)
380 {
381     if (!m_impl->m_session)
382         return;
383     m_impl->m_session->setCodec(codec);
384 }
385 
setColorScheme(const QString & origName)386 void QTermWidget::setColorScheme(const QString& origName)
387 {
388     const ColorScheme *cs = 0;
389 
390     const bool isFile = QFile::exists(origName);
391     const QString& name = isFile ?
392             QFileInfo(origName).baseName() :
393             origName;
394 
395     // avoid legacy (int) solution
396     if (!availableColorSchemes().contains(name))
397     {
398         if (isFile)
399         {
400             if (ColorSchemeManager::instance()->loadCustomColorScheme(origName))
401                 cs = ColorSchemeManager::instance()->findColorScheme(name);
402             else
403                 qWarning () << Q_FUNC_INFO
404                         << "cannot load color scheme from"
405                         << origName;
406         }
407 
408         if (!cs)
409             cs = ColorSchemeManager::instance()->defaultColorScheme();
410     }
411     else
412         cs = ColorSchemeManager::instance()->findColorScheme(name);
413 
414     if (! cs)
415     {
416         QMessageBox::information(this,
417                                  tr("Color Scheme Error"),
418                                  tr("Cannot load color scheme: %1").arg(name));
419         return;
420     }
421     ColorEntry table[TABLE_COLORS];
422     cs->getColorTable(table);
423     m_impl->m_terminalDisplay->setColorTable(table);
424 }
425 
availableColorSchemes()426 QStringList QTermWidget::availableColorSchemes()
427 {
428     QStringList ret;
429     foreach (const ColorScheme* cs, ColorSchemeManager::instance()->allColorSchemes())
430         ret.append(cs->name());
431     return ret;
432 }
433 
setSize(int h,int v)434 void QTermWidget::setSize(int h, int v)
435 {
436     if (!m_impl->m_terminalDisplay)
437         return;
438     m_impl->m_terminalDisplay->setSize(h, v);
439 }
440 
setHistorySize(int lines)441 void QTermWidget::setHistorySize(int lines)
442 {
443     if (lines < 0)
444         m_impl->m_session->setHistoryType(HistoryTypeFile());
445     else
446         m_impl->m_session->setHistoryType(HistoryTypeBuffer(lines));
447 }
448 
setScrollBarPosition(ScrollBarPosition pos)449 void QTermWidget::setScrollBarPosition(ScrollBarPosition pos)
450 {
451     if (!m_impl->m_terminalDisplay)
452         return;
453     m_impl->m_terminalDisplay->setScrollBarPosition((TerminalDisplay::ScrollBarPosition)pos);
454 }
455 
scrollToEnd()456 void QTermWidget::scrollToEnd()
457 {
458     if (!m_impl->m_terminalDisplay)
459         return;
460     m_impl->m_terminalDisplay->scrollToEnd();
461 }
462 
sendText(const QString & text)463 void QTermWidget::sendText(const QString &text)
464 {
465     m_impl->m_session->sendText(text);
466 }
467 
resizeEvent(QResizeEvent *)468 void QTermWidget::resizeEvent(QResizeEvent*)
469 {
470 //qDebug("global window resizing...with %d %d", this->size().width(), this->size().height());
471     m_impl->m_terminalDisplay->resize(this->size());
472 }
473 
474 
sessionFinished()475 void QTermWidget::sessionFinished()
476 {
477     emit finished();
478 }
479 
480 
copyClipboard()481 void QTermWidget::copyClipboard()
482 {
483     m_impl->m_terminalDisplay->copyClipboard();
484 }
485 
pasteClipboard()486 void QTermWidget::pasteClipboard()
487 {
488     m_impl->m_terminalDisplay->pasteClipboard();
489 }
490 
pasteSelection()491 void QTermWidget::pasteSelection()
492 {
493     m_impl->m_terminalDisplay->pasteSelection();
494 }
495 
setZoom(int step)496 void QTermWidget::setZoom(int step)
497 {
498     if (!m_impl->m_terminalDisplay)
499         return;
500 
501     QFont font = m_impl->m_terminalDisplay->getVTFont();
502 
503     font.setPointSize(font.pointSize() + step);
504     setTerminalFont(font);
505 }
506 
zoomIn()507 void QTermWidget::zoomIn()
508 {
509     setZoom(STEP_ZOOM);
510 }
511 
zoomOut()512 void QTermWidget::zoomOut()
513 {
514     setZoom(-STEP_ZOOM);
515 }
516 
setKeyBindings(const QString & kb)517 void QTermWidget::setKeyBindings(const QString & kb)
518 {
519     m_impl->m_session->setKeyBindings(kb);
520 }
521 
clear()522 void QTermWidget::clear()
523 {
524     m_impl->m_session->emulation()->reset();
525     m_impl->m_session->refresh();
526     m_impl->m_session->clearHistory();
527 }
528 
setFlowControlEnabled(bool enabled)529 void QTermWidget::setFlowControlEnabled(bool enabled)
530 {
531     m_impl->m_session->setFlowControlEnabled(enabled);
532 }
533 
availableKeyBindings()534 QStringList QTermWidget::availableKeyBindings()
535 {
536     return KeyboardTranslatorManager::instance()->allTranslators();
537 }
538 
keyBindings()539 QString QTermWidget::keyBindings()
540 {
541     return m_impl->m_session->keyBindings();
542 }
543 
toggleShowSearchBar()544 void QTermWidget::toggleShowSearchBar()
545 {
546     m_searchBar->isHidden() ? m_searchBar->show() : m_searchBar->hide();
547 }
548 
flowControlEnabled(void)549 bool QTermWidget::flowControlEnabled(void)
550 {
551     return m_impl->m_session->flowControlEnabled();
552 }
553 
setFlowControlWarningEnabled(bool enabled)554 void QTermWidget::setFlowControlWarningEnabled(bool enabled)
555 {
556     if (flowControlEnabled()) {
557         // Do not show warning label if flow control is disabled
558         m_impl->m_terminalDisplay->setFlowControlWarningEnabled(enabled);
559     }
560 }
561 
setEnvironment(const QStringList & environment)562 void QTermWidget::setEnvironment(const QStringList& environment)
563 {
564     m_impl->m_session->setEnvironment(environment);
565 }
566 
setMotionAfterPasting(int action)567 void QTermWidget::setMotionAfterPasting(int action)
568 {
569     m_impl->m_terminalDisplay->setMotionAfterPasting((Konsole::MotionAfterPasting) action);
570 }
571 
historyLinesCount()572 int QTermWidget::historyLinesCount()
573 {
574     return m_impl->m_terminalDisplay->screenWindow()->screen()->getHistLines();
575 }
576 
screenColumnsCount()577 int QTermWidget::screenColumnsCount()
578 {
579     return m_impl->m_terminalDisplay->screenWindow()->screen()->getColumns();
580 }
581 
screenLinesCount()582 int QTermWidget::screenLinesCount()
583 {
584     return m_impl->m_terminalDisplay->screenWindow()->screen()->getLines();
585 }
586 
setSelectionStart(int row,int column)587 void QTermWidget::setSelectionStart(int row, int column)
588 {
589     m_impl->m_terminalDisplay->screenWindow()->screen()->setSelectionStart(column, row, true);
590 }
591 
setSelectionEnd(int row,int column)592 void QTermWidget::setSelectionEnd(int row, int column)
593 {
594     m_impl->m_terminalDisplay->screenWindow()->screen()->setSelectionEnd(column, row);
595 }
596 
getSelectionStart(int & row,int & column)597 void QTermWidget::getSelectionStart(int& row, int& column)
598 {
599     m_impl->m_terminalDisplay->screenWindow()->screen()->getSelectionStart(column, row);
600 }
601 
getSelectionEnd(int & row,int & column)602 void QTermWidget::getSelectionEnd(int& row, int& column)
603 {
604     m_impl->m_terminalDisplay->screenWindow()->screen()->getSelectionEnd(column, row);
605 }
606 
selectedText(bool preserveLineBreaks)607 QString QTermWidget::selectedText(bool preserveLineBreaks)
608 {
609     return m_impl->m_terminalDisplay->screenWindow()->screen()->selectedText(preserveLineBreaks);
610 }
611 
setMonitorActivity(bool monitor)612 void QTermWidget::setMonitorActivity(bool monitor)
613 {
614     m_impl->m_session->setMonitorActivity(monitor);
615 }
616 
setMonitorSilence(bool monitor)617 void QTermWidget::setMonitorSilence(bool monitor)
618 {
619     m_impl->m_session->setMonitorSilence(monitor);
620 }
621 
setSilenceTimeout(int seconds)622 void QTermWidget::setSilenceTimeout(int seconds)
623 {
624     m_impl->m_session->setMonitorSilenceSeconds(seconds);
625 }
626 
getHotSpotAt(const QPoint & pos) const627 Filter::HotSpot* QTermWidget::getHotSpotAt(const QPoint &pos) const
628 {
629     int row = 0, column = 0;
630     m_impl->m_terminalDisplay->getCharacterPosition(pos, row, column);
631     return getHotSpotAt(row, column);
632 }
633 
getHotSpotAt(int row,int column) const634 Filter::HotSpot* QTermWidget::getHotSpotAt(int row, int column) const
635 {
636     return m_impl->m_terminalDisplay->filterChain()->hotSpotAt(row, column);
637 }
638 
getPtySlaveFd() const639 int QTermWidget::getPtySlaveFd() const
640 {
641     return m_impl->m_session->getPtySlaveFd();
642 }
643