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