1 | /* |
2 | This file is part of Konsole |
3 | |
4 | Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> |
5 | Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> |
6 | Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de> |
7 | |
8 | This program is free software; you can redistribute it and/or modify |
9 | it under the terms of the GNU General Public License as published by |
10 | the Free Software Foundation; either version 2 of the License, or |
11 | (at your option) any later version. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along with this program; if not, write to the Free Software |
20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
21 | 02110-1301 USA. |
22 | */ |
23 | |
24 | // Own |
25 | #include "Session.h" |
26 | |
27 | // Standard |
28 | #include <stdlib.h> |
29 | #include <signal.h> |
30 | #include <unistd.h> |
31 | |
32 | // Qt |
33 | #include <QApplication> |
34 | #include <QtGui/QColor> |
35 | #include <QtCore/QDir> |
36 | #include <QtCore/QFile> |
37 | #include <QtCore/QStringList> |
38 | #include <QtDBus/QtDBus> |
39 | |
40 | // KDE |
41 | #include <KDebug> |
42 | #include <KLocalizedString> |
43 | #include <KNotification> |
44 | #include <KRun> |
45 | #include <KShell> |
46 | #include <KProcess> |
47 | #include <KStandardDirs> |
48 | #include <KConfigGroup> |
49 | |
50 | // Konsole |
51 | #include <sessionadaptor.h> |
52 | |
53 | #include "ProcessInfo.h" |
54 | #include "Pty.h" |
55 | #include "TerminalDisplay.h" |
56 | #include "ShellCommand.h" |
57 | #include "Vt102Emulation.h" |
58 | #include "ZModemDialog.h" |
59 | #include "History.h" |
60 | |
61 | using namespace Konsole; |
62 | |
63 | int Session::lastSessionId = 0; |
64 | |
65 | // HACK This is copied out of QUuid::createUuid with reseeding forced. |
66 | // Required because color schemes repeatedly seed the RNG... |
67 | // ...with a constant. |
68 | QUuid createUuid() |
69 | { |
70 | static const int intbits = sizeof(int) * 8; |
71 | static int randbits = 0; |
72 | if (!randbits) { |
73 | int max = RAND_MAX; |
74 | do { |
75 | ++randbits; |
76 | } while ((max = max >> 1)); |
77 | } |
78 | |
79 | qsrand(uint(QDateTime::currentDateTime().toTime_t())); |
80 | qrand(); // Skip first |
81 | |
82 | QUuid result; |
83 | uint* data = &(result.data1); |
84 | int chunks = 16 / sizeof(uint); |
85 | while (chunks--) { |
86 | uint randNumber = 0; |
87 | for (int filled = 0; filled < intbits; filled += randbits) |
88 | randNumber |= qrand() << filled; |
89 | *(data + chunks) = randNumber; |
90 | } |
91 | |
92 | result.data4[0] = (result.data4[0] & 0x3F) | 0x80; // UV_DCE |
93 | result.data3 = (result.data3 & 0x0FFF) | 0x4000; // UV_Random |
94 | |
95 | return result; |
96 | } |
97 | |
98 | Session::Session(QObject* parent) : |
99 | QObject(parent) |
100 | , _shellProcess(0) |
101 | , _emulation(0) |
102 | , _monitorActivity(false) |
103 | , _monitorSilence(false) |
104 | , _notifiedActivity(false) |
105 | , _silenceSeconds(10) |
106 | , _autoClose(true) |
107 | , _closePerUserRequest(false) |
108 | , _addToUtmp(true) |
109 | , _flowControlEnabled(true) |
110 | , _sessionId(0) |
111 | , _sessionProcessInfo(0) |
112 | , _foregroundProcessInfo(0) |
113 | , _foregroundPid(0) |
114 | , _zmodemBusy(false) |
115 | , _zmodemProc(0) |
116 | , _zmodemProgress(0) |
117 | , _hasDarkBackground(false) |
118 | { |
119 | _uniqueIdentifier = createUuid(); |
120 | |
121 | //prepare DBus communication |
122 | new SessionAdaptor(this); |
123 | _sessionId = ++lastSessionId; |
124 | QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/" ) + QString::number(_sessionId), this); |
125 | |
126 | //create emulation backend |
127 | _emulation = new Vt102Emulation(); |
128 | |
129 | connect(_emulation, SIGNAL(titleChanged(int,QString)), |
130 | this, SLOT(setUserTitle(int,QString))); |
131 | connect(_emulation, SIGNAL(stateSet(int)), |
132 | this, SLOT(activityStateSet(int))); |
133 | connect(_emulation, SIGNAL(zmodemDetected()), |
134 | this, SLOT(fireZModemDetected())); |
135 | connect(_emulation, SIGNAL(changeTabTextColorRequest(int)), |
136 | this, SIGNAL(changeTabTextColorRequest(int))); |
137 | connect(_emulation, SIGNAL(profileChangeCommandReceived(QString)), |
138 | this, SIGNAL(profileChangeCommandReceived(QString))); |
139 | connect(_emulation, SIGNAL(flowControlKeyPressed(bool)), |
140 | this, SLOT(updateFlowControlState(bool))); |
141 | connect(_emulation, SIGNAL(primaryScreenInUse(bool)), |
142 | this, SLOT(onPrimaryScreenInUse(bool))); |
143 | connect(_emulation, SIGNAL(selectionChanged(QString)), |
144 | this, SIGNAL(selectionChanged(QString))); |
145 | connect(_emulation, SIGNAL(imageResizeRequest(QSize)), |
146 | this, SIGNAL(resizeRequest(QSize))); |
147 | |
148 | //create new teletype for I/O with shell process |
149 | openTeletype(-1); |
150 | |
151 | //setup timer for monitoring session activity & silence |
152 | _silenceTimer = new QTimer(this); |
153 | _silenceTimer->setSingleShot(true); |
154 | connect(_silenceTimer, SIGNAL(timeout()), this, SLOT(silenceTimerDone())); |
155 | |
156 | _activityTimer = new QTimer(this); |
157 | _activityTimer->setSingleShot(true); |
158 | connect(_activityTimer, SIGNAL(timeout()), this, SLOT(activityTimerDone())); |
159 | } |
160 | |
161 | Session::~Session() |
162 | { |
163 | delete _foregroundProcessInfo; |
164 | delete _sessionProcessInfo; |
165 | delete _emulation; |
166 | delete _shellProcess; |
167 | delete _zmodemProc; |
168 | } |
169 | |
170 | void Session::openTeletype(int fd) |
171 | { |
172 | if (isRunning()) { |
173 | kWarning() << "Attempted to open teletype in a running session." ; |
174 | return; |
175 | } |
176 | |
177 | delete _shellProcess; |
178 | |
179 | if (fd < 0) |
180 | _shellProcess = new Pty(); |
181 | else |
182 | _shellProcess = new Pty(fd); |
183 | |
184 | _shellProcess->setUtf8Mode(_emulation->utf8()); |
185 | |
186 | // connect the I/O between emulator and pty process |
187 | connect(_shellProcess, SIGNAL(receivedData(const char*,int)), |
188 | this, SLOT(onReceiveBlock(const char*,int))); |
189 | connect(_emulation, SIGNAL(sendData(const char*,int)), |
190 | _shellProcess, SLOT(sendData(const char*,int))); |
191 | |
192 | // UTF8 mode |
193 | connect(_emulation, SIGNAL(useUtf8Request(bool)), |
194 | _shellProcess, SLOT(setUtf8Mode(bool))); |
195 | |
196 | // get notified when the pty process is finished |
197 | connect(_shellProcess, SIGNAL(finished(int,QProcess::ExitStatus)), |
198 | this, SLOT(done(int,QProcess::ExitStatus))); |
199 | |
200 | // emulator size |
201 | connect(_emulation, SIGNAL(imageSizeChanged(int,int)), |
202 | this, SLOT(updateWindowSize(int,int))); |
203 | connect(_emulation, SIGNAL(imageSizeInitialized()), |
204 | this, SLOT(run())); |
205 | } |
206 | |
207 | WId Session::windowId() const |
208 | { |
209 | // Returns a window ID for this session which is used |
210 | // to set the WINDOWID environment variable in the shell |
211 | // process. |
212 | // |
213 | // Sessions can have multiple views or no views, which means |
214 | // that a single ID is not always going to be accurate. |
215 | // |
216 | // If there are no views, the window ID is just 0. If |
217 | // there are multiple views, then the window ID for the |
218 | // top-level window which contains the first view is |
219 | // returned |
220 | |
221 | if (_views.count() == 0) { |
222 | return 0; |
223 | } else { |
224 | QWidget* window = _views.first(); |
225 | |
226 | Q_ASSERT(window); |
227 | |
228 | while (window->parentWidget() != 0) |
229 | window = window->parentWidget(); |
230 | |
231 | return window->winId(); |
232 | } |
233 | } |
234 | |
235 | void Session::setDarkBackground(bool darkBackground) |
236 | { |
237 | _hasDarkBackground = darkBackground; |
238 | } |
239 | |
240 | bool Session::isRunning() const |
241 | { |
242 | return _shellProcess && (_shellProcess->state() == QProcess::Running); |
243 | } |
244 | |
245 | void Session::setCodec(QTextCodec* codec) |
246 | { |
247 | emulation()->setCodec(codec); |
248 | } |
249 | |
250 | bool Session::setCodec(QByteArray name) |
251 | { |
252 | QTextCodec* codec = QTextCodec::codecForName(name); |
253 | |
254 | if (codec) { |
255 | setCodec(codec); |
256 | return true; |
257 | } else { |
258 | return false; |
259 | } |
260 | } |
261 | |
262 | QByteArray Session::codec() |
263 | { |
264 | return _emulation->codec()->name(); |
265 | } |
266 | |
267 | void Session::setProgram(const QString& program) |
268 | { |
269 | _program = ShellCommand::expand(program); |
270 | } |
271 | |
272 | void Session::setArguments(const QStringList& arguments) |
273 | { |
274 | _arguments = ShellCommand::expand(arguments); |
275 | } |
276 | |
277 | void Session::setInitialWorkingDirectory(const QString& dir) |
278 | { |
279 | _initialWorkingDir = KShell::tildeExpand(ShellCommand::expand(dir)); |
280 | } |
281 | |
282 | QString Session::currentWorkingDirectory() |
283 | { |
284 | // only returned cached value |
285 | if (_currentWorkingDir.isEmpty()) |
286 | updateWorkingDirectory(); |
287 | |
288 | return _currentWorkingDir; |
289 | } |
290 | ProcessInfo* Session::updateWorkingDirectory() |
291 | { |
292 | ProcessInfo* process = getProcessInfo(); |
293 | |
294 | const QString currentDir = process->validCurrentDir(); |
295 | if (currentDir != _currentWorkingDir) { |
296 | _currentWorkingDir = currentDir; |
297 | emit currentDirectoryChanged(_currentWorkingDir); |
298 | } |
299 | |
300 | return process; |
301 | } |
302 | |
303 | QList<TerminalDisplay*> Session::views() const |
304 | { |
305 | return _views; |
306 | } |
307 | |
308 | void Session::addView(TerminalDisplay* widget) |
309 | { |
310 | Q_ASSERT(!_views.contains(widget)); |
311 | |
312 | _views.append(widget); |
313 | |
314 | // connect emulation - view signals and slots |
315 | connect(widget, SIGNAL(keyPressedSignal(QKeyEvent*)), |
316 | _emulation, SLOT(sendKeyEvent(QKeyEvent*))); |
317 | connect(widget, SIGNAL(mouseSignal(int,int,int,int)), |
318 | _emulation, SLOT(sendMouseEvent(int,int,int,int))); |
319 | connect(widget, SIGNAL(sendStringToEmu(const char*)), |
320 | _emulation, SLOT(sendString(const char*))); |
321 | |
322 | // allow emulation to notify view when the foreground process |
323 | // indicates whether or not it is interested in mouse signals |
324 | connect(_emulation, SIGNAL(programUsesMouseChanged(bool)), |
325 | widget, SLOT(setUsesMouse(bool))); |
326 | |
327 | widget->setUsesMouse(_emulation->programUsesMouse()); |
328 | |
329 | connect(_emulation, SIGNAL(programBracketedPasteModeChanged(bool)), |
330 | widget, SLOT(setBracketedPasteMode(bool))); |
331 | |
332 | widget->setBracketedPasteMode(_emulation->programBracketedPasteMode()); |
333 | |
334 | widget->setScreenWindow(_emulation->createWindow()); |
335 | |
336 | //connect view signals and slots |
337 | connect(widget, SIGNAL(changedContentSizeSignal(int,int)), |
338 | this, SLOT(onViewSizeChange(int,int))); |
339 | |
340 | connect(widget, SIGNAL(destroyed(QObject*)), |
341 | this, SLOT(viewDestroyed(QObject*))); |
342 | } |
343 | |
344 | void Session::viewDestroyed(QObject* view) |
345 | { |
346 | // the received QObject has already been destroyed, so using |
347 | // qobject_cast<> does not work here |
348 | TerminalDisplay* display = static_cast<TerminalDisplay*>(view); |
349 | |
350 | Q_ASSERT(_views.contains(display)); |
351 | |
352 | removeView(display); |
353 | } |
354 | |
355 | void Session::removeView(TerminalDisplay* widget) |
356 | { |
357 | _views.removeAll(widget); |
358 | |
359 | disconnect(widget, 0, this, 0); |
360 | |
361 | // disconnect |
362 | // - key presses signals from widget |
363 | // - mouse activity signals from widget |
364 | // - string sending signals from widget |
365 | // |
366 | // ... and any other signals connected in addView() |
367 | disconnect(widget, 0, _emulation, 0); |
368 | |
369 | // disconnect state change signals emitted by emulation |
370 | disconnect(_emulation, 0, widget, 0); |
371 | |
372 | // close the session automatically when the last view is removed |
373 | if (_views.count() == 0) { |
374 | close(); |
375 | } |
376 | } |
377 | |
378 | // Upon a KPty error, there is no description on what that error was... |
379 | // Check to see if the given program is executable. |
380 | QString Session::checkProgram(const QString& program) |
381 | { |
382 | QString exec = program; |
383 | |
384 | if (exec.isEmpty()) |
385 | return QString(); |
386 | |
387 | QFileInfo info(exec); |
388 | if (info.isAbsolute() && info.exists() && info.isExecutable()) { |
389 | return exec; |
390 | } |
391 | |
392 | exec = KRun::binaryName(exec, false); |
393 | exec = KShell::tildeExpand(exec); |
394 | QString pexec = KStandardDirs::findExe(exec); |
395 | if (pexec.isEmpty()) { |
396 | kError() << i18n("Could not find binary: " ) << exec; |
397 | return QString(); |
398 | } |
399 | |
400 | return exec; |
401 | } |
402 | |
403 | void Session::terminalWarning(const QString& message) |
404 | { |
405 | static const QByteArray warningText = i18nc("@info:shell Alert the user with red color text" , "Warning: " ).toLocal8Bit(); |
406 | QByteArray messageText = message.toLocal8Bit(); |
407 | |
408 | static const char redPenOn[] = "\033[1m\033[31m" ; |
409 | static const char redPenOff[] = "\033[0m" ; |
410 | |
411 | _emulation->receiveData(redPenOn, qstrlen(redPenOn)); |
412 | _emulation->receiveData("\n\r\n\r" , 4); |
413 | _emulation->receiveData(warningText.constData(), qstrlen(warningText.constData())); |
414 | _emulation->receiveData(messageText.constData(), qstrlen(messageText.constData())); |
415 | _emulation->receiveData("\n\r\n\r" , 4); |
416 | _emulation->receiveData(redPenOff, qstrlen(redPenOff)); |
417 | } |
418 | |
419 | QString Session::shellSessionId() const |
420 | { |
421 | QString friendlyUuid(_uniqueIdentifier.toString()); |
422 | friendlyUuid.remove('-').remove('{').remove('}'); |
423 | |
424 | return friendlyUuid; |
425 | } |
426 | |
427 | void Session::run() |
428 | { |
429 | // extra safeguard for potential bug. |
430 | if (isRunning()) { |
431 | kWarning() << "Attempted to re-run an already running session." ; |
432 | return; |
433 | } |
434 | |
435 | //check that everything is in place to run the session |
436 | if (_program.isEmpty()) { |
437 | kWarning() << "Program to run not set." ; |
438 | } |
439 | if (_arguments.isEmpty()) { |
440 | kWarning() << "No command line arguments specified." ; |
441 | } |
442 | if (_uniqueIdentifier.isNull()) { |
443 | _uniqueIdentifier = createUuid(); |
444 | } |
445 | |
446 | const int CHOICE_COUNT = 3; |
447 | // if '_program' is empty , fall back to default shell. If that is not set |
448 | // then fall back to /bin/sh |
449 | #ifndef _WIN32 |
450 | QString programs[CHOICE_COUNT] = {_program, qgetenv("SHELL" ), "/bin/sh" }; |
451 | #else |
452 | // on windows, fall back to %COMSPEC% or to cmd.exe |
453 | QString programs[CHOICE_COUNT] = {_program, qgetenv("COMSPEC" ), "cmd.exe" }; |
454 | #endif |
455 | QString exec; |
456 | int choice = 0; |
457 | while (choice < CHOICE_COUNT) { |
458 | exec = checkProgram(programs[choice]); |
459 | if (exec.isEmpty()) |
460 | choice++; |
461 | else |
462 | break; |
463 | } |
464 | |
465 | // if a program was specified via setProgram(), but it couldn't be found, print a warning |
466 | if (choice != 0 && choice < CHOICE_COUNT && !_program.isEmpty()) { |
467 | terminalWarning(i18n("Could not find '%1', starting '%2' instead. Please check your profile settings." , _program, exec)); |
468 | // if none of the choices are available, print a warning |
469 | } else if (choice == CHOICE_COUNT) { |
470 | terminalWarning(i18n("Could not find an interactive shell to start." )); |
471 | return; |
472 | } |
473 | |
474 | // if no arguments are specified, fall back to program name |
475 | QStringList arguments = _arguments.join(QChar(' ')).isEmpty() ? |
476 | QStringList() << exec : |
477 | _arguments; |
478 | |
479 | if (!_initialWorkingDir.isEmpty()) { |
480 | _shellProcess->setInitialWorkingDirectory(_initialWorkingDir); |
481 | } else { |
482 | _shellProcess->setInitialWorkingDirectory(QDir::currentPath()); |
483 | } |
484 | |
485 | _shellProcess->setFlowControlEnabled(_flowControlEnabled); |
486 | _shellProcess->setEraseChar(_emulation->eraseChar()); |
487 | _shellProcess->setUseUtmp(_addToUtmp); |
488 | |
489 | // this is not strictly accurate use of the COLORFGBG variable. This does not |
490 | // tell the terminal exactly which colors are being used, but instead approximates |
491 | // the color scheme as "black on white" or "white on black" depending on whether |
492 | // the background color is deemed dark or not |
493 | const QString backgroundColorHint = _hasDarkBackground ? "COLORFGBG=15;0" : "COLORFGBG=0;15" ; |
494 | addEnvironmentEntry(backgroundColorHint); |
495 | |
496 | addEnvironmentEntry(QString("SHELL_SESSION_ID=%1" ).arg(shellSessionId())); |
497 | |
498 | addEnvironmentEntry(QString("WINDOWID=%1" ).arg(QString::number(windowId()))); |
499 | |
500 | const QString dbusService = QDBusConnection::sessionBus().baseService(); |
501 | addEnvironmentEntry(QString("KONSOLE_DBUS_SERVICE=%1" ).arg(dbusService)); |
502 | |
503 | const QString dbusObject = QString("/Sessions/%1" ).arg(QString::number(_sessionId)); |
504 | addEnvironmentEntry(QString("KONSOLE_DBUS_SESSION=%1" ).arg(dbusObject)); |
505 | |
506 | int result = _shellProcess->start(exec, arguments, _environment); |
507 | if (result < 0) { |
508 | terminalWarning(i18n("Could not start program '%1' with arguments '%2'." , exec, arguments.join(" " ))); |
509 | terminalWarning(_shellProcess->errorString()); |
510 | return; |
511 | } |
512 | |
513 | _shellProcess->setWriteable(false); // We are reachable via kwrited. |
514 | |
515 | emit started(); |
516 | } |
517 | |
518 | void Session::setUserTitle(int what, const QString& caption) |
519 | { |
520 | //set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle ) |
521 | bool modified = false; |
522 | |
523 | if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) { |
524 | if (_userTitle != caption) { |
525 | _userTitle = caption; |
526 | modified = true; |
527 | } |
528 | } |
529 | |
530 | if ((what == IconNameAndWindowTitle) || (what == IconName)) { |
531 | if (_iconText != caption) { |
532 | _iconText = caption; |
533 | modified = true; |
534 | } |
535 | } |
536 | |
537 | if (what == TextColor || what == BackgroundColor) { |
538 | QString colorString = caption.section(';', 0, 0); |
539 | QColor color = QColor(colorString); |
540 | if (color.isValid()) { |
541 | if (what == TextColor) |
542 | emit changeForegroundColorRequest(color); |
543 | else |
544 | emit changeBackgroundColorRequest(color); |
545 | } |
546 | } |
547 | |
548 | if (what == SessionName) { |
549 | if (_localTabTitleFormat != caption) { |
550 | _localTabTitleFormat = caption; |
551 | setTitle(Session::DisplayedTitleRole, caption); |
552 | modified = true; |
553 | } |
554 | } |
555 | |
556 | /* I don't believe this has ever worked in KDE 4.x |
557 | if (what == 31) { |
558 | QString cwd = caption; |
559 | cwd = cwd.replace(QRegExp("^~"), QDir::homePath()); |
560 | emit openUrlRequest(cwd); |
561 | }*/ |
562 | |
563 | /* The below use of 32 works but appears to non-standard. |
564 | It is from a commit from 2004 c20973eca8776f9b4f15bee5fdcb5a3205aa69de |
565 | */ |
566 | // change icon via \033]32;Icon\007 |
567 | if (what == SessionIcon) { |
568 | if (_iconName != caption) { |
569 | _iconName = caption; |
570 | |
571 | modified = true; |
572 | } |
573 | } |
574 | |
575 | if (what == ProfileChange) { |
576 | emit profileChangeCommandReceived(caption); |
577 | return; |
578 | } |
579 | |
580 | if (modified) |
581 | emit titleChanged(); |
582 | } |
583 | |
584 | QString Session::userTitle() const |
585 | { |
586 | return _userTitle; |
587 | } |
588 | void Session::setTabTitleFormat(TabTitleContext context , const QString& format) |
589 | { |
590 | if (context == LocalTabTitle) |
591 | _localTabTitleFormat = format; |
592 | else if (context == RemoteTabTitle) |
593 | _remoteTabTitleFormat = format; |
594 | } |
595 | QString Session::tabTitleFormat(TabTitleContext context) const |
596 | { |
597 | if (context == LocalTabTitle) |
598 | return _localTabTitleFormat; |
599 | else if (context == RemoteTabTitle) |
600 | return _remoteTabTitleFormat; |
601 | |
602 | return QString(); |
603 | } |
604 | |
605 | void Session::silenceTimerDone() |
606 | { |
607 | //FIXME: The idea here is that the notification popup will appear to tell the user than output from |
608 | //the terminal has stopped and the popup will disappear when the user activates the session. |
609 | // |
610 | //This breaks with the addition of multiple views of a session. The popup should disappear |
611 | //when any of the views of the session becomes active |
612 | |
613 | //FIXME: Make message text for this notification and the activity notification more descriptive. |
614 | if (_monitorSilence) { |
615 | KNotification::event("Silence" , i18n("Silence in session '%1'" , _nameTitle), QPixmap(), |
616 | QApplication::activeWindow(), |
617 | KNotification::CloseWhenWidgetActivated); |
618 | emit stateChanged(NOTIFYSILENCE); |
619 | } else { |
620 | emit stateChanged(NOTIFYNORMAL); |
621 | } |
622 | } |
623 | |
624 | void Session::activityTimerDone() |
625 | { |
626 | _notifiedActivity = false; |
627 | } |
628 | |
629 | void Session::updateFlowControlState(bool suspended) |
630 | { |
631 | if (suspended) { |
632 | if (flowControlEnabled()) { |
633 | foreach(TerminalDisplay * display, _views) { |
634 | if (display->flowControlWarningEnabled()) |
635 | display->outputSuspended(true); |
636 | } |
637 | } |
638 | } else { |
639 | foreach(TerminalDisplay * display, _views) { |
640 | display->outputSuspended(false); |
641 | } |
642 | } |
643 | } |
644 | |
645 | void Session::onPrimaryScreenInUse(bool use) |
646 | { |
647 | emit primaryScreenInUse(use); |
648 | } |
649 | |
650 | void Session::activityStateSet(int state) |
651 | { |
652 | // TODO: should this hardcoded interval be user configurable? |
653 | const int activityMaskInSeconds = 15; |
654 | |
655 | if (state == NOTIFYBELL) { |
656 | emit bellRequest(i18n("Bell in session '%1'" , _nameTitle)); |
657 | } else if (state == NOTIFYACTIVITY) { |
658 | if (_monitorActivity && !_notifiedActivity) { |
659 | KNotification::event("Activity" , i18n("Activity in session '%1'" , _nameTitle), QPixmap(), |
660 | QApplication::activeWindow(), |
661 | KNotification::CloseWhenWidgetActivated); |
662 | |
663 | // mask activity notification for a while to avoid flooding |
664 | _notifiedActivity = true; |
665 | _activityTimer->start(activityMaskInSeconds * 1000); |
666 | } |
667 | |
668 | // reset the counter for monitoring continuous silence since there is activity |
669 | if (_monitorSilence) { |
670 | _silenceTimer->start(_silenceSeconds * 1000); |
671 | } |
672 | } |
673 | |
674 | if (state == NOTIFYACTIVITY && !_monitorActivity) |
675 | state = NOTIFYNORMAL; |
676 | if (state == NOTIFYSILENCE && !_monitorSilence) |
677 | state = NOTIFYNORMAL; |
678 | |
679 | emit stateChanged(state); |
680 | } |
681 | |
682 | void Session::onViewSizeChange(int /*height*/, int /*width*/) |
683 | { |
684 | updateTerminalSize(); |
685 | } |
686 | |
687 | void Session::updateTerminalSize() |
688 | { |
689 | int minLines = -1; |
690 | int minColumns = -1; |
691 | |
692 | // minimum number of lines and columns that views require for |
693 | // their size to be taken into consideration ( to avoid problems |
694 | // with new view widgets which haven't yet been set to their correct size ) |
695 | const int VIEW_LINES_THRESHOLD = 2; |
696 | const int VIEW_COLUMNS_THRESHOLD = 2; |
697 | |
698 | //select largest number of lines and columns that will fit in all visible views |
699 | foreach(TerminalDisplay* view, _views) { |
700 | if (view->isHidden() == false && |
701 | view->lines() >= VIEW_LINES_THRESHOLD && |
702 | view->columns() >= VIEW_COLUMNS_THRESHOLD) { |
703 | minLines = (minLines == -1) ? view->lines() : qMin(minLines , view->lines()); |
704 | minColumns = (minColumns == -1) ? view->columns() : qMin(minColumns , view->columns()); |
705 | view->processFilters(); |
706 | } |
707 | } |
708 | |
709 | // backend emulation must have a _terminal of at least 1 column x 1 line in size |
710 | if (minLines > 0 && minColumns > 0) { |
711 | _emulation->setImageSize(minLines , minColumns); |
712 | } |
713 | } |
714 | void Session::updateWindowSize(int lines, int columns) |
715 | { |
716 | Q_ASSERT(lines > 0 && columns > 0); |
717 | _shellProcess->setWindowSize(columns, lines); |
718 | } |
719 | void Session::refresh() |
720 | { |
721 | // attempt to get the shell process to redraw the display |
722 | // |
723 | // this requires the program running in the shell |
724 | // to cooperate by sending an update in response to |
725 | // a window size change |
726 | // |
727 | // the window size is changed twice, first made slightly larger and then |
728 | // resized back to its normal size so that there is actually a change |
729 | // in the window size (some shells do nothing if the |
730 | // new and old sizes are the same) |
731 | // |
732 | // if there is a more 'correct' way to do this, please |
733 | // send an email with method or patches to konsole-devel@kde.org |
734 | |
735 | const QSize existingSize = _shellProcess->windowSize(); |
736 | _shellProcess->setWindowSize(existingSize.width() + 1, existingSize.height()); |
737 | usleep(500); // introduce small delay to avoid changing size too quickly |
738 | _shellProcess->setWindowSize(existingSize.width(), existingSize.height()); |
739 | } |
740 | |
741 | void Session::sendSignal(int signal) |
742 | { |
743 | const ProcessInfo* process = getProcessInfo(); |
744 | bool ok = false; |
745 | int pid; |
746 | pid = process->foregroundPid(&ok); |
747 | |
748 | if (ok) { |
749 | ::kill(pid, signal); |
750 | } |
751 | } |
752 | |
753 | bool Session::kill(int signal) |
754 | { |
755 | if (_shellProcess->pid() <= 0) |
756 | return false; |
757 | |
758 | int result = ::kill(_shellProcess->pid(), signal); |
759 | |
760 | if (result == 0) { |
761 | if (_shellProcess->waitForFinished(1000)) |
762 | return true; |
763 | else |
764 | return false; |
765 | } else { |
766 | return false; |
767 | } |
768 | } |
769 | |
770 | void Session::close() |
771 | { |
772 | if (isRunning()) { |
773 | if (!closeInNormalWay()) |
774 | closeInForceWay(); |
775 | } else { |
776 | // terminal process has finished, just close the session |
777 | QTimer::singleShot(1, this, SIGNAL(finished())); |
778 | } |
779 | } |
780 | |
781 | bool Session::closeInNormalWay() |
782 | { |
783 | _autoClose = true; |
784 | _closePerUserRequest = true; |
785 | |
786 | // for the possible case where following events happen in sequence: |
787 | // |
788 | // 1). the terminal process crashes |
789 | // 2). the tab stays open and displays warning message |
790 | // 3). the user closes the tab explicitly |
791 | // |
792 | if (!isRunning()) { |
793 | emit finished(); |
794 | return true; |
795 | } |
796 | |
797 | if (kill(SIGHUP)) { |
798 | return true; |
799 | } else { |
800 | kWarning() << "Process " << _shellProcess->pid() << " did not die with SIGHUP" ; |
801 | _shellProcess->closePty(); |
802 | return (_shellProcess->waitForFinished(1000)); |
803 | } |
804 | } |
805 | |
806 | bool Session::closeInForceWay() |
807 | { |
808 | _autoClose = true; |
809 | _closePerUserRequest = true; |
810 | |
811 | if (kill(SIGKILL)) { |
812 | return true; |
813 | } else { |
814 | kWarning() << "Process " << _shellProcess->pid() << " did not die with SIGKILL" ; |
815 | return false; |
816 | } |
817 | } |
818 | |
819 | void Session::sendText(const QString& text) const |
820 | { |
821 | _emulation->sendText(text); |
822 | } |
823 | |
824 | void Session::runCommand(const QString& command) const |
825 | { |
826 | _emulation->sendText(command + '\n'); |
827 | } |
828 | |
829 | void Session::sendMouseEvent(int buttons, int column, int line, int eventType) |
830 | { |
831 | _emulation->sendMouseEvent(buttons, column, line, eventType); |
832 | } |
833 | |
834 | void Session::done(int exitCode, QProcess::ExitStatus exitStatus) |
835 | { |
836 | // This slot should be triggered only one time |
837 | disconnect(_shellProcess, SIGNAL(finished(int,QProcess::ExitStatus)), |
838 | this, SLOT(done(int,QProcess::ExitStatus))); |
839 | |
840 | if (!_autoClose) { |
841 | _userTitle = i18nc("@info:shell This session is done" , "Finished" ); |
842 | emit titleChanged(); |
843 | return; |
844 | } |
845 | |
846 | if (_closePerUserRequest) { |
847 | emit finished(); |
848 | return; |
849 | } |
850 | |
851 | QString message; |
852 | |
853 | if (exitCode != 0) { |
854 | if (exitStatus != QProcess::NormalExit) |
855 | message = i18n("Program '%1' crashed." , _program); |
856 | else |
857 | message = i18n("Program '%1' exited with status %2." , _program, exitCode); |
858 | |
859 | //FIXME: See comments in Session::silenceTimerDone() |
860 | KNotification::event("Finished" , message , QPixmap(), |
861 | QApplication::activeWindow(), |
862 | KNotification::CloseWhenWidgetActivated); |
863 | } |
864 | |
865 | if (exitStatus != QProcess::NormalExit) { |
866 | // this seeming duplicated line is for the case when exitCode is 0 |
867 | message = i18n("Program '%1' crashed." , _program); |
868 | terminalWarning(message); |
869 | } else { |
870 | emit finished(); |
871 | } |
872 | } |
873 | |
874 | Emulation* Session::emulation() const |
875 | { |
876 | return _emulation; |
877 | } |
878 | |
879 | QString Session::keyBindings() const |
880 | { |
881 | return _emulation->keyBindings(); |
882 | } |
883 | |
884 | QStringList Session::environment() const |
885 | { |
886 | return _environment; |
887 | } |
888 | |
889 | void Session::setEnvironment(const QStringList& environment) |
890 | { |
891 | _environment = environment; |
892 | } |
893 | |
894 | void Session::addEnvironmentEntry(const QString& entry) |
895 | { |
896 | _environment << entry; |
897 | } |
898 | |
899 | int Session::sessionId() const |
900 | { |
901 | return _sessionId; |
902 | } |
903 | |
904 | void Session::setKeyBindings(const QString& name) |
905 | { |
906 | _emulation->setKeyBindings(name); |
907 | } |
908 | |
909 | void Session::setTitle(TitleRole role , const QString& newTitle) |
910 | { |
911 | if (title(role) != newTitle) { |
912 | if (role == NameRole) |
913 | _nameTitle = newTitle; |
914 | else if (role == DisplayedTitleRole) |
915 | _displayTitle = newTitle; |
916 | |
917 | emit titleChanged(); |
918 | } |
919 | } |
920 | |
921 | QString Session::title(TitleRole role) const |
922 | { |
923 | if (role == NameRole) |
924 | return _nameTitle; |
925 | else if (role == DisplayedTitleRole) |
926 | return _displayTitle; |
927 | else |
928 | return QString(); |
929 | } |
930 | |
931 | ProcessInfo* Session::getProcessInfo() |
932 | { |
933 | ProcessInfo* process = 0; |
934 | |
935 | if (isForegroundProcessActive()) { |
936 | process = _foregroundProcessInfo; |
937 | } else { |
938 | updateSessionProcessInfo(); |
939 | process = _sessionProcessInfo; |
940 | } |
941 | |
942 | return process; |
943 | } |
944 | |
945 | void Session::updateSessionProcessInfo() |
946 | { |
947 | Q_ASSERT(_shellProcess); |
948 | |
949 | bool ok; |
950 | // The checking for pid changing looks stupid, but it is needed |
951 | // at the moment to workaround the problem that processId() might |
952 | // return 0 |
953 | if (!_sessionProcessInfo || |
954 | (processId() != 0 && processId() != _sessionProcessInfo->pid(&ok))) { |
955 | delete _sessionProcessInfo; |
956 | _sessionProcessInfo = ProcessInfo::newInstance(processId()); |
957 | _sessionProcessInfo->setUserHomeDir(); |
958 | } |
959 | _sessionProcessInfo->update(); |
960 | } |
961 | |
962 | bool Session::updateForegroundProcessInfo() |
963 | { |
964 | Q_ASSERT(_shellProcess); |
965 | |
966 | const int foregroundPid = _shellProcess->foregroundProcessGroup(); |
967 | if (foregroundPid != _foregroundPid) { |
968 | delete _foregroundProcessInfo; |
969 | _foregroundProcessInfo = ProcessInfo::newInstance(foregroundPid); |
970 | _foregroundPid = foregroundPid; |
971 | } |
972 | |
973 | if (_foregroundProcessInfo) { |
974 | _foregroundProcessInfo->update(); |
975 | return _foregroundProcessInfo->isValid(); |
976 | } else { |
977 | return false; |
978 | } |
979 | } |
980 | |
981 | bool Session::isRemote() |
982 | { |
983 | ProcessInfo* process = getProcessInfo(); |
984 | |
985 | bool ok = false; |
986 | return (process->name(&ok) == "ssh" && ok); |
987 | } |
988 | |
989 | QString Session::getDynamicTitle() |
990 | { |
991 | // update current directory from process |
992 | ProcessInfo* process = updateWorkingDirectory(); |
993 | |
994 | // format tab titles using process info |
995 | bool ok = false; |
996 | QString title; |
997 | if (process->name(&ok) == "ssh" && ok) { |
998 | SSHProcessInfo sshInfo(*process); |
999 | title = sshInfo.format(tabTitleFormat(Session::RemoteTabTitle)); |
1000 | } else { |
1001 | title = process->format(tabTitleFormat(Session::LocalTabTitle)); |
1002 | } |
1003 | |
1004 | return title; |
1005 | } |
1006 | |
1007 | KUrl Session::getUrl() |
1008 | { |
1009 | QString path; |
1010 | |
1011 | updateSessionProcessInfo(); |
1012 | if (_sessionProcessInfo->isValid()) { |
1013 | bool ok = false; |
1014 | |
1015 | // check if foreground process is bookmark-able |
1016 | if (isForegroundProcessActive()) { |
1017 | // for remote connections, save the user and host |
1018 | // bright ideas to get the directory at the other end are welcome :) |
1019 | if (_foregroundProcessInfo->name(&ok) == "ssh" && ok) { |
1020 | SSHProcessInfo sshInfo(*_foregroundProcessInfo); |
1021 | |
1022 | path = "ssh://" + sshInfo.userName() + '@' + sshInfo.host(); |
1023 | |
1024 | QString port = sshInfo.port(); |
1025 | if (!port.isEmpty() && port != "22" ) { |
1026 | path.append(':' + port); |
1027 | } |
1028 | } else { |
1029 | path = _foregroundProcessInfo->currentDir(&ok); |
1030 | if (!ok) |
1031 | path.clear(); |
1032 | } |
1033 | } else { // otherwise use the current working directory of the shell process |
1034 | path = _sessionProcessInfo->currentDir(&ok); |
1035 | if (!ok) |
1036 | path.clear(); |
1037 | } |
1038 | } |
1039 | |
1040 | return KUrl(path); |
1041 | } |
1042 | |
1043 | void Session::setIconName(const QString& iconName) |
1044 | { |
1045 | if (iconName != _iconName) { |
1046 | _iconName = iconName; |
1047 | emit titleChanged(); |
1048 | } |
1049 | } |
1050 | |
1051 | void Session::setIconText(const QString& iconText) |
1052 | { |
1053 | _iconText = iconText; |
1054 | } |
1055 | |
1056 | QString Session::iconName() const |
1057 | { |
1058 | return _iconName; |
1059 | } |
1060 | |
1061 | QString Session::iconText() const |
1062 | { |
1063 | return _iconText; |
1064 | } |
1065 | |
1066 | void Session::setHistoryType(const HistoryType& hType) |
1067 | { |
1068 | _emulation->setHistory(hType); |
1069 | } |
1070 | |
1071 | const HistoryType& Session::historyType() const |
1072 | { |
1073 | return _emulation->history(); |
1074 | } |
1075 | |
1076 | void Session::clearHistory() |
1077 | { |
1078 | _emulation->clearHistory(); |
1079 | } |
1080 | |
1081 | QStringList Session::arguments() const |
1082 | { |
1083 | return _arguments; |
1084 | } |
1085 | |
1086 | QString Session::program() const |
1087 | { |
1088 | return _program; |
1089 | } |
1090 | |
1091 | bool Session::isMonitorActivity() const |
1092 | { |
1093 | return _monitorActivity; |
1094 | } |
1095 | bool Session::isMonitorSilence() const |
1096 | { |
1097 | return _monitorSilence; |
1098 | } |
1099 | |
1100 | void Session::setMonitorActivity(bool monitor) |
1101 | { |
1102 | if (_monitorActivity == monitor) |
1103 | return; |
1104 | |
1105 | _monitorActivity = monitor; |
1106 | _notifiedActivity = false; |
1107 | |
1108 | // This timer is meaningful only after activity has been notified |
1109 | _activityTimer->stop(); |
1110 | |
1111 | activityStateSet(NOTIFYNORMAL); |
1112 | } |
1113 | |
1114 | void Session::setMonitorSilence(bool monitor) |
1115 | { |
1116 | if (_monitorSilence == monitor) |
1117 | return; |
1118 | |
1119 | _monitorSilence = monitor; |
1120 | if (_monitorSilence) { |
1121 | _silenceTimer->start(_silenceSeconds * 1000); |
1122 | } else { |
1123 | _silenceTimer->stop(); |
1124 | } |
1125 | |
1126 | activityStateSet(NOTIFYNORMAL); |
1127 | } |
1128 | |
1129 | void Session::setMonitorSilenceSeconds(int seconds) |
1130 | { |
1131 | _silenceSeconds = seconds; |
1132 | if (_monitorSilence) { |
1133 | _silenceTimer->start(_silenceSeconds * 1000); |
1134 | } |
1135 | } |
1136 | |
1137 | void Session::setAddToUtmp(bool add) |
1138 | { |
1139 | _addToUtmp = add; |
1140 | } |
1141 | |
1142 | void Session::setAutoClose(bool close) |
1143 | { |
1144 | _autoClose = close; |
1145 | } |
1146 | |
1147 | bool Session::autoClose() const |
1148 | { |
1149 | return _autoClose; |
1150 | } |
1151 | |
1152 | void Session::setFlowControlEnabled(bool enabled) |
1153 | { |
1154 | _flowControlEnabled = enabled; |
1155 | |
1156 | if (_shellProcess) |
1157 | _shellProcess->setFlowControlEnabled(_flowControlEnabled); |
1158 | |
1159 | emit flowControlEnabledChanged(enabled); |
1160 | } |
1161 | bool Session::flowControlEnabled() const |
1162 | { |
1163 | if (_shellProcess) |
1164 | return _shellProcess->flowControlEnabled(); |
1165 | else |
1166 | return _flowControlEnabled; |
1167 | } |
1168 | void Session::fireZModemDetected() |
1169 | { |
1170 | if (!_zmodemBusy) { |
1171 | QTimer::singleShot(10, this, SIGNAL(zmodemDetected())); |
1172 | _zmodemBusy = true; |
1173 | } |
1174 | } |
1175 | |
1176 | void Session::cancelZModem() |
1177 | { |
1178 | _shellProcess->sendData("\030\030\030\030" , 4); // Abort |
1179 | _zmodemBusy = false; |
1180 | } |
1181 | |
1182 | void Session::startZModem(const QString& zmodem, const QString& dir, const QStringList& list) |
1183 | { |
1184 | _zmodemBusy = true; |
1185 | _zmodemProc = new KProcess(); |
1186 | _zmodemProc->setOutputChannelMode(KProcess::SeparateChannels); |
1187 | |
1188 | *_zmodemProc << zmodem << "-v" << list; |
1189 | |
1190 | if (!dir.isEmpty()) |
1191 | _zmodemProc->setWorkingDirectory(dir); |
1192 | |
1193 | connect(_zmodemProc, SIGNAL(readyReadStandardOutput()), |
1194 | this, SLOT(zmodemReadAndSendBlock())); |
1195 | connect(_zmodemProc, SIGNAL(readyReadStandardError()), |
1196 | this, SLOT(zmodemReadStatus())); |
1197 | connect(_zmodemProc, SIGNAL(finished(int,QProcess::ExitStatus)), |
1198 | this, SLOT(zmodemFinished())); |
1199 | |
1200 | _zmodemProc->start(); |
1201 | |
1202 | disconnect(_shellProcess, SIGNAL(receivedData(const char*,int)), |
1203 | this, SLOT(onReceiveBlock(const char*,int))); |
1204 | connect(_shellProcess, SIGNAL(receivedData(const char*,int)), |
1205 | this, SLOT(zmodemReceiveBlock(const char*,int))); |
1206 | |
1207 | _zmodemProgress = new ZModemDialog(QApplication::activeWindow(), false, |
1208 | i18n("ZModem Progress" )); |
1209 | |
1210 | connect(_zmodemProgress, SIGNAL(user1Clicked()), |
1211 | this, SLOT(zmodemFinished())); |
1212 | |
1213 | _zmodemProgress->show(); |
1214 | } |
1215 | |
1216 | void Session::zmodemReadAndSendBlock() |
1217 | { |
1218 | _zmodemProc->setReadChannel(QProcess::StandardOutput); |
1219 | QByteArray data = _zmodemProc->readAll(); |
1220 | |
1221 | if (data.count() == 0) |
1222 | return; |
1223 | |
1224 | _shellProcess->sendData(data.constData(), data.count()); |
1225 | } |
1226 | |
1227 | void Session::zmodemReadStatus() |
1228 | { |
1229 | _zmodemProc->setReadChannel(QProcess::StandardError); |
1230 | QByteArray msg = _zmodemProc->readAll(); |
1231 | while (!msg.isEmpty()) { |
1232 | int i = msg.indexOf('\015'); |
1233 | int j = msg.indexOf('\012'); |
1234 | QByteArray txt; |
1235 | if ((i != -1) && ((j == -1) || (i < j))) { |
1236 | msg = msg.mid(i + 1); |
1237 | } else if (j != -1) { |
1238 | txt = msg.left(j); |
1239 | msg = msg.mid(j + 1); |
1240 | } else { |
1241 | txt = msg; |
1242 | msg.truncate(0); |
1243 | } |
1244 | if (!txt.isEmpty()) |
1245 | _zmodemProgress->addProgressText(QString::fromLocal8Bit(txt)); |
1246 | } |
1247 | } |
1248 | |
1249 | void Session::zmodemReceiveBlock(const char* data, int len) |
1250 | { |
1251 | QByteArray bytes(data, len); |
1252 | |
1253 | _zmodemProc->write(bytes); |
1254 | } |
1255 | |
1256 | void Session::zmodemFinished() |
1257 | { |
1258 | /* zmodemFinished() is called by QProcess's finished() and |
1259 | ZModemDialog's user1Clicked(). Therefore, an invocation by |
1260 | user1Clicked() will recursively invoke this function again |
1261 | when the KProcess is deleted! */ |
1262 | if (_zmodemProc) { |
1263 | KProcess* process = _zmodemProc; |
1264 | _zmodemProc = 0; // Set _zmodemProc to 0 avoid recursive invocations! |
1265 | _zmodemBusy = false; |
1266 | delete process; // Now, the KProcess may be disposed safely. |
1267 | |
1268 | disconnect(_shellProcess, SIGNAL(receivedData(const char*,int)), |
1269 | this , SLOT(zmodemReceiveBlock(const char*,int))); |
1270 | connect(_shellProcess, SIGNAL(receivedData(const char*,int)), |
1271 | this, SLOT(onReceiveBlock(const char*,int))); |
1272 | |
1273 | _shellProcess->sendData("\030\030\030\030" , 4); // Abort |
1274 | _shellProcess->sendData("\001\013\n" , 3); // Try to get prompt back |
1275 | _zmodemProgress->transferDone(); |
1276 | } |
1277 | } |
1278 | |
1279 | void Session::onReceiveBlock(const char* buf, int len) |
1280 | { |
1281 | _emulation->receiveData(buf, len); |
1282 | } |
1283 | |
1284 | QSize Session::size() |
1285 | { |
1286 | return _emulation->imageSize(); |
1287 | } |
1288 | |
1289 | void Session::setSize(const QSize& size) |
1290 | { |
1291 | if ((size.width() <= 1) || (size.height() <= 1)) |
1292 | return; |
1293 | |
1294 | emit resizeRequest(size); |
1295 | } |
1296 | |
1297 | QSize Session::preferredSize() const |
1298 | { |
1299 | return _preferredSize; |
1300 | } |
1301 | |
1302 | void Session::setPreferredSize(const QSize& size) |
1303 | { |
1304 | _preferredSize = size; |
1305 | } |
1306 | |
1307 | int Session::processId() const |
1308 | { |
1309 | return _shellProcess->pid(); |
1310 | } |
1311 | |
1312 | void Session::setTitle(int role , const QString& title) |
1313 | { |
1314 | switch (role) { |
1315 | case 0: |
1316 | this->setTitle(Session::NameRole, title); |
1317 | break; |
1318 | case 1: |
1319 | this->setTitle(Session::DisplayedTitleRole, title); |
1320 | |
1321 | // without these, that title will be overridden by the expansion of |
1322 | // title format shortly after, which will confuses users. |
1323 | _localTabTitleFormat = title; |
1324 | _remoteTabTitleFormat = title; |
1325 | |
1326 | break; |
1327 | } |
1328 | } |
1329 | |
1330 | QString Session::title(int role) const |
1331 | { |
1332 | switch (role) { |
1333 | case 0: |
1334 | return this->title(Session::NameRole); |
1335 | case 1: |
1336 | return this->title(Session::DisplayedTitleRole); |
1337 | default: |
1338 | return QString(); |
1339 | } |
1340 | } |
1341 | |
1342 | void Session::setTabTitleFormat(int context , const QString& format) |
1343 | { |
1344 | switch (context) { |
1345 | case 0: |
1346 | this->setTabTitleFormat(Session::LocalTabTitle, format); |
1347 | break; |
1348 | case 1: |
1349 | this->setTabTitleFormat(Session::RemoteTabTitle, format); |
1350 | break; |
1351 | } |
1352 | } |
1353 | |
1354 | QString Session::tabTitleFormat(int context) const |
1355 | { |
1356 | switch (context) { |
1357 | case 0: |
1358 | return this->tabTitleFormat(Session::LocalTabTitle); |
1359 | case 1: |
1360 | return this->tabTitleFormat(Session::RemoteTabTitle); |
1361 | default: |
1362 | return QString(); |
1363 | } |
1364 | } |
1365 | |
1366 | void Session::setHistorySize(int lines) |
1367 | { |
1368 | if (lines < 0) { |
1369 | setHistoryType(HistoryTypeFile()); |
1370 | } else if (lines == 0) { |
1371 | setHistoryType(HistoryTypeNone()); |
1372 | } else { |
1373 | setHistoryType(CompactHistoryType(lines)); |
1374 | } |
1375 | } |
1376 | |
1377 | int Session::historySize() const |
1378 | { |
1379 | const HistoryType& currentHistory = historyType(); |
1380 | |
1381 | if (currentHistory.isEnabled()) { |
1382 | if (currentHistory.isUnlimited()) { |
1383 | return -1; |
1384 | } else { |
1385 | return currentHistory.maximumLineCount(); |
1386 | } |
1387 | } else { |
1388 | return 0; |
1389 | } |
1390 | } |
1391 | |
1392 | int Session::foregroundProcessId() |
1393 | { |
1394 | int pid; |
1395 | |
1396 | bool ok = false; |
1397 | pid = getProcessInfo()->pid(&ok); |
1398 | if (!ok) |
1399 | pid = -1; |
1400 | |
1401 | return pid; |
1402 | } |
1403 | |
1404 | bool Session::isForegroundProcessActive() |
1405 | { |
1406 | // foreground process info is always updated after this |
1407 | return updateForegroundProcessInfo() && (processId() != _foregroundPid); |
1408 | } |
1409 | |
1410 | QString Session::foregroundProcessName() |
1411 | { |
1412 | QString name; |
1413 | |
1414 | if (updateForegroundProcessInfo()) { |
1415 | bool ok = false; |
1416 | name = _foregroundProcessInfo->name(&ok); |
1417 | if (!ok) |
1418 | name.clear(); |
1419 | } |
1420 | |
1421 | return name; |
1422 | } |
1423 | |
1424 | void Session::saveSession(KConfigGroup& group) |
1425 | { |
1426 | group.writePathEntry("WorkingDir" , currentWorkingDirectory()); |
1427 | group.writeEntry("LocalTab" , tabTitleFormat(LocalTabTitle)); |
1428 | group.writeEntry("RemoteTab" , tabTitleFormat(RemoteTabTitle)); |
1429 | group.writeEntry("SessionGuid" , _uniqueIdentifier.toString()); |
1430 | group.writeEntry("Encoding" , QString(codec())); |
1431 | } |
1432 | |
1433 | void Session::restoreSession(KConfigGroup& group) |
1434 | { |
1435 | QString value; |
1436 | |
1437 | value = group.readPathEntry("WorkingDir" , QString()); |
1438 | if (!value.isEmpty()) setInitialWorkingDirectory(value); |
1439 | value = group.readEntry("LocalTab" ); |
1440 | if (!value.isEmpty()) setTabTitleFormat(LocalTabTitle, value); |
1441 | value = group.readEntry("RemoteTab" ); |
1442 | if (!value.isEmpty()) setTabTitleFormat(RemoteTabTitle, value); |
1443 | value = group.readEntry("SessionGuid" ); |
1444 | if (!value.isEmpty()) _uniqueIdentifier = QUuid(value); |
1445 | value = group.readEntry("Encoding" ); |
1446 | if (!value.isEmpty()) setCodec(value.toUtf8()); |
1447 | } |
1448 | |
1449 | SessionGroup::SessionGroup(QObject* parent) |
1450 | : QObject(parent), _masterMode(0) |
1451 | { |
1452 | } |
1453 | SessionGroup::~SessionGroup() |
1454 | { |
1455 | } |
1456 | int SessionGroup::masterMode() const |
1457 | { |
1458 | return _masterMode; |
1459 | } |
1460 | QList<Session*> SessionGroup::sessions() const |
1461 | { |
1462 | return _sessions.keys(); |
1463 | } |
1464 | bool SessionGroup::masterStatus(Session* session) const |
1465 | { |
1466 | return _sessions[session]; |
1467 | } |
1468 | |
1469 | void SessionGroup::addSession(Session* session) |
1470 | { |
1471 | connect(session, SIGNAL(finished()), this, SLOT(sessionFinished())); |
1472 | _sessions.insert(session, false); |
1473 | } |
1474 | void SessionGroup::removeSession(Session* session) |
1475 | { |
1476 | disconnect(session, SIGNAL(finished()), this, SLOT(sessionFinished())); |
1477 | setMasterStatus(session, false); |
1478 | _sessions.remove(session); |
1479 | } |
1480 | void SessionGroup::sessionFinished() |
1481 | { |
1482 | Session* session = qobject_cast<Session*>(sender()); |
1483 | Q_ASSERT(session); |
1484 | removeSession(session); |
1485 | } |
1486 | void SessionGroup::setMasterMode(int mode) |
1487 | { |
1488 | _masterMode = mode; |
1489 | } |
1490 | QList<Session*> SessionGroup::masters() const |
1491 | { |
1492 | return _sessions.keys(true); |
1493 | } |
1494 | void SessionGroup::setMasterStatus(Session* session , bool master) |
1495 | { |
1496 | const bool wasMaster = _sessions[session]; |
1497 | |
1498 | if (wasMaster == master) { |
1499 | // No status change -> nothing to do. |
1500 | return; |
1501 | } |
1502 | _sessions[session] = master; |
1503 | |
1504 | if (master) { |
1505 | connect(session->emulation(), SIGNAL(sendData(const char*,int)), |
1506 | this, SLOT(forwardData(const char*,int))); |
1507 | } else { |
1508 | disconnect(session->emulation(), SIGNAL(sendData(const char*,int)), |
1509 | this, SLOT(forwardData(const char*,int))); |
1510 | } |
1511 | } |
1512 | void SessionGroup::forwardData(const char* data, int size) |
1513 | { |
1514 | static bool _inForwardData = false; |
1515 | if (_inForwardData) { // Avoid recursive calls among session groups! |
1516 | // A recursive call happens when a master in group A calls forwardData() |
1517 | // in group B. If one of the destination sessions in group B is also a |
1518 | // master of a group including the master session of group A, this would |
1519 | // again call forwardData() in group A, and so on. |
1520 | return; |
1521 | } |
1522 | |
1523 | _inForwardData = true; |
1524 | foreach(Session* other, _sessions.keys()) { |
1525 | if (!_sessions[other]) { |
1526 | other->emulation()->sendString(data, size); |
1527 | } |
1528 | } |
1529 | _inForwardData = false; |
1530 | } |
1531 | |
1532 | #include "Session.moc" |
1533 | |
1534 | |